Skip to content

Commit

Permalink
Merge pull request #54 from edx/ashultz0/data-last-updated
Browse files Browse the repository at this point in the history
feat: generate last-updated timestamp into ai hook div
  • Loading branch information
ashultz0 authored Aug 29, 2023
2 parents eb82c4c + 1b2431e commit 2dc69b0
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 27 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ Change Log
Unreleased
**********

3.4.0 – 2023-08-30
**********************************************

* Include last updated timestamp in summary hook HTML, derived from the blocks.
* Also somewhat reformats timestamps in the handler return to conform to ISO standard.


3.3.1 – 2023-08-21
**********************************************

Expand Down
2 changes: 1 addition & 1 deletion ai_aside/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
A plugin containing xblocks and apps supporting GPT and other LLM use on edX.
"""

__version__ = '3.3.1'
__version__ = '3.4.0'

default_app_config = "ai_aside.apps.AiAsideConfig"
67 changes: 46 additions & 21 deletions ai_aside/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
data-course-id="{{data_course_id}}"
data-content-id="{{data_content_id}}"
data-handler-url="{{data_handler_url}}"
data-last-updated="{{data_last_updated}}"
>
</div>
</div>
Expand All @@ -42,7 +43,7 @@


def _format_date(date):
return date.strftime('%Y-%m-%d %H:%M:%S') if isinstance(date, datetime) else None
return date.isoformat() if isinstance(date, datetime) else None


def _staff_user(block):
Expand Down Expand Up @@ -132,6 +133,46 @@ def _check_summarizable(block):
return False


def _render_hook_fragment(handler_url, block, summary_items):
"""
Create hook Fragment from block and summarized children.
Gathers data for the summary hook HTML, passes it into _render_summary
to get the HTML and packages that into a Fragment.
"""
last_published = getattr(block, 'published_on', None)
last_edited = getattr(block, 'edited_on', None)
for item in summary_items:
published = item['published_on']
edited = item['edited_on']
if published and published > last_published:
last_published = published
if edited and edited > last_edited:
last_edited = edited

# we only need to know when the last time was that anything happened
last_updated = last_published
if last_edited > last_published:
last_updated = last_edited

usage_id = block.scope_ids.usage_id

fragment = Fragment('')
fragment.add_content(
_render_summary(
{
'data_url_api': settings.SUMMARY_HOOK_HOST,
'data_course_id': usage_id.course_key,
'data_content_id': usage_id,
'data_handler_url': handler_url,
'data_last_updated': _format_date(last_updated),
'js_url': settings.SUMMARY_HOOK_HOST + settings.SUMMARY_HOOK_JS_PATH,
}
)
)
return fragment


class SummaryHookAside(XBlockAside):
"""
XBlock aside that injects AI summary javascript.
Expand All @@ -155,7 +196,7 @@ def summary_handler(self, request=None, suffix=None): # pylint: disable=unused-
return Response(status=404)

published_on = getattr(block, 'published_on', None)
edited_on = getattr(block, 'published_on', None)
edited_on = getattr(block, 'edited_on', None)

data = []

Expand Down Expand Up @@ -205,31 +246,15 @@ def _student_view_can_throw(self, block):
This function can throw exceptions.
"""
fragment = Fragment('')
length, items = _parse_children_contents(block)

# Check if there is content that worths summarizing
length, _ = _parse_children_contents(block)
if length < settings.SUMMARY_HOOK_MIN_SIZE:
return fragment
return Fragment('')

usage_id = block.scope_ids.usage_id

log.info(f'Summary hook injecting into {usage_id}')

handler_url = self._summary_handler_url()

fragment.add_content(
_render_summary(
{
'data_url_api': settings.SUMMARY_HOOK_HOST,
'data_course_id': usage_id.course_key,
'data_content_id': usage_id,
'data_handler_url': handler_url,
'js_url': settings.SUMMARY_HOOK_HOST + settings.SUMMARY_HOOK_JS_PATH,
}
)
)
return fragment
return _render_hook_fragment(self._summary_handler_url(), block, items)

def _summary_handler_url(self):
"""
Expand Down
58 changes: 53 additions & 5 deletions tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@
from unittest.mock import MagicMock, patch

from django.test import TestCase, override_settings
from opaque_keys.edx.keys import UsageKey

from ai_aside.block import _check_summarizable, _extract_child_contents, _format_date, _parse_children_contents
from ai_aside.block import (
_check_summarizable,
_extract_child_contents,
_format_date,
_parse_children_contents,
_render_hook_fragment,
)

fake_transcript = 'This is the text version from the transcript'
date1 = datetime(2023, 1, 2, 3, 4, 5)
date2 = datetime(2023, 6, 7, 8, 9, 10)


def fake_get_transcript(child, lang=None, output_format='SRT', youtube_id=None): # pylint: disable=unused-argument
Expand Down Expand Up @@ -39,12 +48,19 @@ class FakeBlock:
"Fake block for testing, returns given children"
def __init__(self, children):
self.children = children
self.scope_ids = lambda: None
self.scope_ids.usage_id = UsageKey.from_string('block-v1:edX+A+B+type@vertical+block@verticalD')
self.edited_on = date1
self.published_on = date1

def get_children(self):
return self.children


@override_settings(SUMMARY_HOOK_MIN_SIZE=40, HTML_TAGS_TO_REMOVE=['script', 'style', 'test'])
@override_settings(SUMMARY_HOOK_MIN_SIZE=40,
SUMMARY_HOOK_HOST='http://hookhost',
SUMMARY_HOOK_JS_PATH='/jspath',
HTML_TAGS_TO_REMOVE=['script', 'style', 'test'])
class TestSummaryHookAside(TestCase):
"""Summary hook aside tests"""
def setUp(self):
Expand All @@ -62,9 +78,8 @@ def setUp(self):
patch.dict('sys.modules', modules).start()

def test_format_date(self):
date = datetime(2023, 5, 1, 12, 0, 0)
formatted_date = _format_date(date)
self.assertEqual(formatted_date, '2023-05-01 12:00:00')
formatted_date = _format_date(date1)
self.assertEqual(formatted_date, '2023-01-02T03:04:05')

def test_format_date_with_invalid_input(self):
invalid_date = '2023-05-01'
Expand Down Expand Up @@ -269,6 +284,39 @@ def test_parse_children_contents_with_invalid_children(self):
self.assertEqual(length, 0)
self.assertEqual(items, [])

def test_render_hook_fragment(self):
block = FakeBlock([])
items = [{
'published_on': date1,
'edited_on': date1,
}, {
'published_on': date2,
'edited_on': date1,
}]
expected = '''
<div>&nbsp;</div>
<div class="summary-hook">
<div summary-launch>
<div id="launch-summary-button"
data-url-api="http://hookhost"
data-course-id="course-v1:edX+A+B"
data-content-id="block-v1:edX+A+B+type@vertical+block@verticalD"
data-handler-url="http://handler.url"
data-last-updated="2023-06-07T08:09:10"
>
</div>
</div>
<div id="ai-spot-root"></div>
<script type="text/javascript" src="http://hookhost/jspath" defer="defer"></script>
</div>
'''
fragment = _render_hook_fragment('http://handler.url', block, items)
self.assertEqual(
# join and split to ignore whitespace differences
"".join(fragment.body_html()).split(),
"".join(expected).split()
)


@override_settings(SUMMARY_HOOK_MIN_SIZE=40, HTML_TAGS_TO_REMOVE=['script', 'style', 'test'])
class TestSummaryHookAsideMissingTranscript(TestCase):
Expand Down

0 comments on commit 2dc69b0

Please sign in to comment.