Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 393831d
Author: Jason Altekruse <[email protected]>
Date:   Wed Jan 10 13:03:50 2024 -0600

    Cleanup, would like to turn some of the logging changes in a separate PR

commit 2c2baf1
Author: Jason Altekruse <[email protected]>
Date:   Wed Jan 10 11:45:30 2024 -0600

    Squashed commit of the following:

    commit 429bd06
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Jan 9 15:37:12 2024 -0600

        Remove big files

        - being pulled in from temporary CDN during review, will decide on final location during discussion in PR review

    commit 30a72ed
    Author: Jason Altekruse <[email protected]>
    Date:   Sun Jan 7 21:37:01 2024 -0600

        point to GH pages temporarily until we get a real release of DoenetML up on NPM

    commit c7091d0
    Author: Jason Altekruse <[email protected]>
    Date:   Mon Dec 4 14:49:41 2023 -0600

        Give a valid response if no student page state is stored yet

    commit d33ed6d
    Author: Jason Altekruse <[email protected]>
    Date:   Mon Nov 27 23:17:30 2023 -0600

        recalling state works!

    commit 3b35d6b
    Author: Jason Altekruse <[email protected]>
    Date:   Mon Nov 27 22:19:10 2023 -0600

        TODO - come back to this state and figure out why a 500 server error ends up with a useless payload of {"detail":{}}

        WIP - trying to get state loading from the server

    commit a920052
    Merge: 2eabe19 7d76814
    Author: Jason Altekruse <[email protected]>
    Date:   Wed Nov 22 11:21:28 2023 -0600

        Merge remote-tracking branch 'origin/main' into doenet-question-type

         Conflicts:
        	bases/rsptx/web2py_server/applications/runestone/static/js/admin.js
        	poetry.lock
        	projects/interactives/poetry.lock
        	projects/interactives/pyproject.toml
        	projects/w2p_login_assign_grade/poetry.lock
        	projects/w2p_login_assign_grade/pyproject.toml
        	pyproject.toml

    commit 2eabe19
    Author: Jason Altekruse <[email protected]>
    Date:   Wed Nov 22 11:12:43 2023 -0600

        Debugging inconsistent experience having grades calculated as a student

        - last time I tried hunting this down the dockerized version was behaving different than the dstarted version of the server

    commit 9ae91b1
    Author: Jason Altekruse <[email protected]>
    Date:   Wed Nov 22 11:11:54 2023 -0600

        WIP - some pointers to where file uploads are currently done by students, should be able to be repurposed to allow image uploads for doenet content

    commit 4e159ff
    Author: Jason Altekruse <[email protected]>
    Date:   Wed Nov 22 11:04:57 2023 -0600

        Integrate the 2 panel doenet editor

    commit 3a91363
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Nov 7 12:51:17 2023 -0600

        Auto-grading for Doenet questions

        TODO - remove the copy-paste in this file

        Discuss possibly going as far as consolidating all of the answer tables into 1, I believe that would remove the need for several different mappings and "specialized" copy/pasted functions for each question type that I have needed to find

    commit c86b5bc
    Author: Jason Altekruse <[email protected]>
    Date:   Mon Nov 6 08:56:09 2023 -0600

        WIP - trying to get grades saving and showing up in the gradebook for doenet problems

    commit e4d9155
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Oct 24 22:07:44 2023 -0500

        add useful info to logging of errors

    commit ef97f0d
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Oct 24 22:06:57 2023 -0500

        poetry locks

    commit 0d5771f
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Oct 24 22:06:25 2023 -0500

        WIP - adding doenet to the dropdown, and getting it to show up in the table after being added to an assignment

    commit c14b3f6
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Oct 24 22:04:08 2023 -0500

        fixing other question types hopefully with codeChat downgrade

    commit f1c29dd
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Oct 24 18:01:04 2023 -0500

        making a request to backend!

        Next I need to figure out how I get access to the minimal necessary data for the request, mostly the course name

        from schemas.py
        # Schemas
        # =======
        class LogItemIncoming(BaseModelNone):
            """
            This class defines the schema for what we can expect to get from a logging event.
            Because we are using pydantic type verification happens automatically, if we want
            to add additional constraints we can do so.
            """

            event: str
            act: str
            div_id: str
            course_name: str

    commit ba60d59
    Author: Jason Altekruse <[email protected]>
    Date:   Tue Oct 24 16:37:24 2023 -0500

        I think this is the minimal set of changes to get Doenet rendering

        - making one change over in DoenetML right now to allow passing more config options (redoing change from before I started from the tip of DoenetML recently)

    commit 0c76290
    Author: Jason Altekruse <[email protected]>
    Date:   Thu Oct 19 18:43:07 2023 -0500

        I am confused, but now it lets me update the JS with just building the NPM project and re-building the book?

    commit 789824a
    Author: Jason Altekruse <[email protected]>
    Date:   Thu Oct 19 18:42:36 2023 -0500

        Getting doenet into the standard initialization path for runestone components

        - TODO - refactor doenet-embed to produce an artifact that is importable here, may have a good format with a module and instead need to update the config on this side, this was the error I got

        // TODO fix this, in the meantime including from sphinx_static_files.html
        // ERROR in ./runestone/doenet/js/doenet-standalone.js 240673:19
        //Module parse failed: Unterminated template (240673:19)
        //You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

    commit 142aaf1
    Author: Jason Altekruse <[email protected]>
    Date:   Mon Oct 16 17:15:24 2023 -0500

        lock files, I still can't get it pulling in the old version of codeChat

    commit d91f084
    Author: Jason Altekruse <[email protected]>
    Date:   Mon Oct 16 17:14:59 2023 -0500

        WIP - got doenet showing in the assignment editor
  • Loading branch information
jaltekruse committed Jan 10, 2024
1 parent 7d76814 commit fa87048
Show file tree
Hide file tree
Showing 19 changed files with 565 additions and 13 deletions.
62 changes: 62 additions & 0 deletions bases/rsptx/book_server_api/routers/assessment.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
# -------------------
from bleach import clean
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

# Local application imports
Expand Down Expand Up @@ -67,6 +69,66 @@
tags=["assessment"],
)

@router.get("/getDoenetState")
async def getdoenetstate(request: Request, div_id: str,
course_name: str, event: str,
# sid: Optional[str],
user=Depends(auth_manager)
):
request_data = AssessmentRequest(course=course_name, div_id=div_id, event=event)
# if the user is not logged in an HTTP 401 will be returned.
# Otherwise if the user is an instructor then use the provided
# sid (it could be any student in the class). If none is provided then
# use the user objects username
sid = user.username
if await is_instructor(request):
if request_data.sid:
sid = request_data.sid
else:
if request_data.sid:
# someone is attempting to spoof the api
return make_json_response(
status=status.HTTP_401_UNAUTHORIZED, detail="not an instructor"
)
request_data.sid = sid


row = await fetch_last_answer_table_entry(request_data)
# mypy complains that ``row.id`` doesn't exist (true, but the return type wasn't exact and this does exist).
if not row or row.id is None: # type: ignore
return JSONResponse(
status_code=200, content=jsonable_encoder({"loadedState": False, "success": True})
)
ret = row.dict()
rslogger.debug(f"row is {ret}")
if "timestamp" in ret:
ret["timestamp"] = (
ret["timestamp"].replace(tzinfo=datetime.timezone.utc).isoformat()
)
rslogger.debug(f"timestamp is {ret['timestamp']}")

# Do server-side grading if needed, which restores the answer and feedback.
if feedback := await is_server_feedback(request_data.div_id, request_data.course):
rcd = runestone_component_dict[EVENT2TABLE[request_data.event]]
# The grader should also be defined if there's feedback.
assert rcd.grader
# Use the grader to add server-side feedback to the returned dict.
ret.update(await rcd.grader(row, feedback))

# get grade and instructor feedback if Any
grades = await fetch_question_grade(sid, request_data.course, request_data.div_id)
if grades:
ret["comment"] = grades.comment
ret["score"] = grades.score

real_ret = ret["answer"]["state"]
real_ret["success"] = True
real_ret["loadedState"] = True
rslogger.debug(f"Returning {ret}")
# return make_json_response(detail=ret)
return JSONResponse(
status_code=200, content=jsonable_encoder(real_ret)
)

# getAssessResults
# ----------------
Expand Down
2 changes: 1 addition & 1 deletion bases/rsptx/book_server_api/routers/rslogging.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async def log_book_event(
if entry.act in ["start", "pause", "resume"]:
# We don't need these in the answer table but want the event to be timedExam.
create_answer_table = False
elif entry.event == "webwork" or entry.event == "hparsonsAnswer":
elif entry.event == "webwork" or entry.event == "hparsonsAnswer" or entry.event == "doenet":
entry.answer = json.loads(useinfo_dict["answer"])

if create_answer_table:
Expand Down
3 changes: 3 additions & 0 deletions bases/rsptx/interactives/runestone/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .datafile import DataFile
from .disqus import DisqusDirective
from .dragndrop import DragNDrop
from .doenet import DoenetDirective
from .fitb import FillInTheBlank
from .groupsub import GroupSubmission
from .hparsons import HParsonsDirective
Expand Down Expand Up @@ -40,6 +41,7 @@


# TODO: clean up - many of the folders are not needed as the files are imported by webpack
# TODO - Jason second's this TODO, I've been confused by duplicates copies of static assets
#
# runestone_static_dirs()
# -----------------------
Expand Down Expand Up @@ -251,6 +253,7 @@ def build(options):
"datafile": DataFile,
"disqus": DisqusDirective,
"dragndrop": DragNDrop,
"doenet": DoenetDirective,
"groupsub": GroupSubmission,
"hparsons": HParsonsDirective,
"parsonsprob": ParsonsProblem,
Expand Down
1 change: 1 addition & 0 deletions bases/rsptx/interactives/runestone/doenet/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .doenet import *
181 changes: 181 additions & 0 deletions bases/rsptx/interactives/runestone/doenet/doenet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@

# *********
# |docname|
# *********
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = "jaltekruse"

from docutils import nodes
from docutils.parsers.rst import directives
from sqlalchemy import Table
from runestone.server.componentdb import (
addQuestionToDB,
addHTMLToDB,
maybeAddToAssignment,
)
from runestone.common.runestonedirective import (
RunestoneIdDirective,
RunestoneIdNode,
)


def setup(app):
app.add_directive("doenet", DoenetDirective)
app.add_node(DoenetNode, html=(visit_hp_html, depart_hp_html))


TEMPLATE_START = """
<div class="runestone">
<div data-component="hparsons" id=%(divid)s data-question_label="%(question_label)s" class="alert alert-warning hparsons_section">
<div class="hp_question">
"""

TEMPLATE_END = """
</div>
<div class='hparsons'></div>
<textarea
%(language)s
%(optional)s
%(dburl)s
%(reuse)s
%(randomize)s
%(blockanswer)s
style="visibility: hidden;">
%(initialsetting)s
</textarea>
</div>
</div>
"""


class DoenetNode(nodes.General, nodes.Element, RunestoneIdNode):
pass


# self for these functions is an instance of the writer class. For example
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
# The node that is passed as a parameter is an instance of our node class.
def visit_hp_html(self, node):

node["delimiter"] = "_start__{}_".format(node["runestone_options"]["divid"])

self.body.append(node["delimiter"])

res = TEMPLATE_START % node["runestone_options"]
self.body.append(res)


def depart_hp_html(self, node):
res = TEMPLATE_END % node["runestone_options"]
self.body.append(res)

addHTMLToDB(
node["runestone_options"]["divid"],
node["runestone_options"]["basecourse"],
"".join(self.body[self.body.index(node["delimiter"]) + 1 :]),
)

self.body.remove(node["delimiter"])


class DoenetDirective(RunestoneIdDirective):
"""
<!-- .. doenet:: doenet-1
-->
1+3000=<answer>4</answer>
"""

required_arguments = 1
optional_arguments = 1
has_content = True
option_spec = RunestoneIdDirective.option_spec.copy()
option_spec.update(
{
"dburl": directives.unchanged,
"language": directives.unchanged,
"reuse": directives.flag,
"randomize": directives.flag,
"blockanswer": directives.unchanged,
}
)

def run(self):
super(DoenetDirective, self).run()
addQuestionToDB(self)

env = self.state.document.settings.env

if "language" in self.options:
self.options["language"] = "data-language='{}'".format(
self.options["language"]
)
else:
self.options["language"] = ""

if "reuse" in self.options:
self.options["reuse"] = ' data-reuse="true"'
else:
self.options["reuse"] = ""

if "randomize" in self.options:
self.options["randomize"] = ' data-randomize="true"'
else:
self.options["randomize"] = ""

if "blockanswer" in self.options:
self.options["blockanswer"] = "data-blockanswer='{}'".format(
self.options["blockanswer"]
)
else:
self.options["blockanswer"] = ""

explain_text = None
if self.content:
if "~~~~" in self.content:
idx = self.content.index("~~~~")
explain_text = self.content[:idx]
self.content = self.content[idx + 1 :]
source = "\n".join(self.content)
else:
source = "\n"

self.explain_text = explain_text or ["Not an Exercise"]

self.options["initialsetting"] = source

# SQL Options
if "dburl" in self.options:
self.options["dburl"] = "data-dburl='{}'".format(self.options["dburl"])
else:
self.options["dburl"] = ""

course_name = env.config.html_context["course_id"]
divid = self.options["divid"]

hpnode = DoenetNode()
hpnode["runestone_options"] = self.options
hpnode["source"], hpnode["line"] = self.state_machine.get_source_and_line(
self.lineno
)
self.add_name(hpnode) # make this divid available as a target for :ref:

maybeAddToAssignment(self)
if explain_text:
self.updateContent()
self.state.nested_parse(explain_text, self.content_offset, hpnode)

return [hpnode]
Loading

0 comments on commit fa87048

Please sign in to comment.