Skip to content

Commit

Permalink
Merge pull request #43 from edx/rijuma/16210-separate-logic-into-api-…
Browse files Browse the repository at this point in the history
…layer

[ACADEMIC-16210] Moved API logic to api module and added check
  • Loading branch information
rijuma authored Jul 28, 2023
2 parents 9d1c3d6 + 3d113d2 commit a110d86
Show file tree
Hide file tree
Showing 23 changed files with 672 additions and 130 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.2.0 – 2023-07-26
**********************************************

Features
=========
* Added the checks for the module settings behind the waffle flag `summaryhook.summaryhook_summaries_configuration`.
* Added is this course configurable endpoint
* Error suppression logs now include block ID
* Missing video transcript is caught earlier in content fetch

Expand Down
46 changes: 46 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,52 @@ For the summary aside to work, you will have to make two changes in the LMS admi

2. You must enable a course waffle flag for each course you want to summarize. ``summaryhook.summaryhook_enabled`` is the main one, ``summaryhook_enabled.summaryhook_staff_only`` can be used if you only want staff to see it.

3. You must enable a course waffle flag if you want to use the feature for enabling/disabling the summary by its settings. ``summaryhook.summaryhook_summaries_configuration``. If this flag is enabled, you can enable/disable the courses and the blocks by its settings.

Aside Settings API
~~~~~~~~~~~~~~~~~~

There are some endpoints that can be used to pinpoint units to be either enabled or disabled based on their configs. The new settings work as follows:
- If a course is enabled, the summary for all the blocks for that course are enabled by default.
- If a course is disabled or the setting does not exists, then the summary for all the blocks from that course are disabled by default.
- If a block has its own settings, it will override any other setting with the one that is saved.
- If a block does not have any settings saved, then the enabled state will fall back to the course's enabled state mentioned above.

The new endpoints for updating these settings are:

Fetch settings
..............

+--------+-------------------------------------+-------------------------------------------------------------------+
| Method | Path | Responses |
+========+=====================================+===================================================================+
| GET | ``ai_aside/v1/:course_id`` | - Code 200: ``{ "success": true }`` |
+--------+-------------------------------------+ - Code 400: ``{ "success": false, "message": "(description)" }`` |
| GET | ``ai_aside/v1/:course_id/:unit_id`` | - Code 404: ``{ "success": false }`` |
+--------+-------------------------------------+-------------------------------------------------------------------+

Update settings
...............

+--------+-------------------------------------+-------------------------------+------------------------------------------------------------------+
| Method | Path | Payload | Responses |
+========+=====================================+===============================+==================================================================+
| POST | ``ai_aside/v1/:course_id`` | ``{ "enabled": true|false }`` | - Code 200: ``{ "success": true }`` |
+--------+-------------------------------------+-------------------------------+ - Code 400: ``{ "success": false, "message": "(description)" }`` |
| POST | ``ai_aside/v1/:course_id/:unit_id`` | ``{ "enabled": true|false }`` | |
+--------+-------------------------------------+-------------------------------+------------------------------------------------------------------+

Delete settings
...............

+--------+-------------------------------------+-------------------------------------------------------------------+
| Method | Path | Responses |
+========+=====================================+===================================================================+
| DELETE | ``ai_aside/v1/:course_id`` | - Code 200: ``{ "success": true }`` |
+--------+-------------------------------------+ - Code 400: ``{ "success": false, "message": "(description)" }`` |
| DELETE | ``ai_aside/v1/:course_id/:unit_id`` | - Code 404: ``{ "success": false }`` |
+--------+-------------------------------------+-------------------------------------------------------------------+

Every time you develop something in this repo
---------------------------------------------
.. code-block::
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.1.0'
__version__ = '3.2.0'

default_app_config = "ai_aside.apps.AiAsideConfig"
19 changes: 16 additions & 3 deletions ai_aside/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
from webob import Response
from xblock.core import XBlock, XBlockAside

from ai_aside.config_api.api import is_summary_enabled
from ai_aside.platform_imports import get_block, get_text_transcript
from ai_aside.text_utils import html_to_text
from ai_aside.waffle import summary_enabled, summary_staff_only
from ai_aside.waffle import summaries_configuration_enabled as ff_is_summary_config_enabled
from ai_aside.waffle import summary_enabled as ff_summary_enabled
from ai_aside.waffle import summary_staff_only as ff_summary_staff_only

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -271,7 +274,17 @@ def _should_apply_can_throw(cls, block):
"""
if getattr(block, 'category', None) != 'vertical':
return False

course_key = block.scope_ids.usage_id.course_key
if _staff_user(block) and summary_staff_only(course_key):
unit_key = block.scope_ids.usage_id

if _staff_user(block) and ff_summary_staff_only(course_key):
return True

if ff_is_summary_config_enabled(course_key):
return is_summary_enabled(course_key, unit_key)

if ff_summary_enabled(course_key):
return True
return summary_enabled(course_key)

return False
File renamed without changes.
142 changes: 142 additions & 0 deletions ai_aside/config_api/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""
Implements an API for updating unit and course settings.
"""
from ai_aside.config_api.internal import NotFoundError, _get_course, _get_unit
from ai_aside.models import AIAsideCourseEnabled, AIAsideUnitEnabled
from ai_aside.waffle import summaries_configuration_enabled


def get_course_settings(course_key):
"""
Gets the settings of a course.
Returns: dictionary of the form:
`{'enabled': bool}`
"""
record = _get_course(course_key)
fields = {
'enabled': record.enabled
}

return fields


def set_course_settings(course_key, settings):
"""
Sets the settings of a course.
Expects: settings to be a dictionary of the form:
`{'enabled': bool}`
Raises NotFoundError if the settings are not found.
"""
enabled = settings['enabled']

if not isinstance(enabled, bool):
raise TypeError

update = {'enabled': enabled}

AIAsideCourseEnabled.objects.update_or_create(
course_key=course_key,
defaults=update,
)


def delete_course_settings(course_key):
"""
Deletes the settings of a course.
Raises NotFoundError if the settings are not found.
"""
record = _get_course(course_key)
record.delete()


def get_unit_settings(course_key, unit_key):
"""
Gets the settings of a unit.
Returns: dictionary of the form:
`{'enabled': bool}`
"""
record = _get_unit(course_key, unit_key)

fields = {
'enabled': record.enabled
}

return fields


def set_unit_settings(course_key, unit_key, settings):
"""
Sets the settings of a course's unit.
Expects: settings as a dictionary of the form:
`{'enabled': bool}`
Raises NotFoundError if the settings are not found.
"""
enabled = settings['enabled']

if not isinstance(enabled, bool):
raise TypeError

settings = {'enabled': enabled}

AIAsideUnitEnabled.objects.update_or_create(
course_key=course_key,
unit_key=unit_key,
defaults=settings,
)


def delete_unit_settings(course_key, unit_key):
"""
Deletes the settings of a unit.
Raises NotFoundError if the settings are not found.
"""
record = _get_unit(course_key, unit_key)
record.delete()


def is_summary_config_enabled(course_key):
"""
Is this course even allowed to configure summaries?
This is just a wrapper for the waffle flag for use
by the views.
"""
return summaries_configuration_enabled(course_key)


def is_summary_enabled(course_key, unit_key=None):
"""
Gets the enabled state of a course's unit.
It considers both the state of a unit's override and a course defaults.
"""

# If the feature flag is disabled, always returns false.
if not summaries_configuration_enabled(course_key):
return False

if unit_key is not None:
try:
unit = _get_unit(course_key, unit_key)

if unit is not None:
return unit.enabled
except NotFoundError:
pass

try:
course = _get_course(course_key)
except NotFoundError:
return False

if course is not None:
return course.enabled

return False
33 changes: 33 additions & 0 deletions ai_aside/config_api/internal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Internal methods for the API.
"""
from ai_aside.models import AIAsideCourseEnabled, AIAsideUnitEnabled


class NotFoundError(Exception):
"Raised when the course/unit is not found in the database"


def _get_course(course_key):
"Private method that gets a course based on an id"
try:
record = AIAsideCourseEnabled.objects.get(
course_key=course_key,
)
except AIAsideCourseEnabled.DoesNotExist as exc:
raise NotFoundError from exc

return record


def _get_unit(course_key, unit_key):
"Private method that gets a unit based on an id"
try:
record = AIAsideUnitEnabled.objects.get(
course_key=course_key,
unit_key=unit_key,
)
except AIAsideUnitEnabled.DoesNotExist as exc:
raise NotFoundError from exc

return record
5 changes: 4 additions & 1 deletion ai_aside/api/urls.py → ai_aside/config_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
"""
from django.urls import re_path

from ai_aside.api.views import CourseEnabledAPIView, UnitEnabledAPIView
from ai_aside.config_api.views import CourseEnabledAPIView, CourseSummaryConfigEnabledAPIView, UnitEnabledAPIView
from ai_aside.constants import COURSE_ID_PATTERN, UNIT_ID_PATTERN

urlpatterns = [
re_path(r'^v1/{course_id}/?$'.format(
course_id=COURSE_ID_PATTERN
), CourseEnabledAPIView.as_view(), name='api-course-settings'),
re_path(r'^v1/{course_id}/configurable/?$'.format(
course_id=COURSE_ID_PATTERN
), CourseSummaryConfigEnabledAPIView.as_view(), name='api-course-configurable'),
re_path(r'^v1/{course_id}/{unit_id}/?$'.format(
course_id=COURSE_ID_PATTERN,
unit_id=UNIT_ID_PATTERN
Expand Down
Loading

0 comments on commit a110d86

Please sign in to comment.