Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/openedx/edx-enterprise in…
Browse files Browse the repository at this point in the history
…to MueezKhan/Encryption-Fields-Added-To-MoodleConfig
  • Loading branch information
MueezKhan246 committed Oct 11, 2023
2 parents 6a8a7b7 + 99a4958 commit 2c3b179
Show file tree
Hide file tree
Showing 25 changed files with 347 additions and 26 deletions.
6 changes: 5 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
# Required: the version of this file's schema.
version: 2

build:
os: "ubuntu-20.04"
tools:
python: "3.8"

# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
Expand All @@ -17,6 +22,5 @@ formats:

# Optionally set the version of Python and requirements required to build your docs
python:
version: "3.8"
install:
- requirements: requirements/doc.txt
32 changes: 32 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ Change Log
Unreleased
----------
[4.6.1]
-------
feat: Added the disable_subject_metadata_transmission flag to CornerstoneEnterpriseCustomerConfiguration.

[4.6.0]
-------
feat: Added enable_source_demo_data_for_analytics_and_lpr field to EnterpriseCustomer.

[4.5.7]
-------
fix: Fixed ChatGPT prompt and a few model modifications for better readability for admins.

[4.5.6]
-------
feat: Added logs for learner completion data post request[moodle]

[4.5.5]
-------
chore: sso orchestrator configs should start inactive and be activated upon successful configuration

[4.5.4]
-------
feat: inactive moodle course instead of true delete

[4.5.3]
-------
feat: added dry run mode for content metadata transmission

[4.5.2]
-------
chore: adding a more flexible way of fetching api request data

[4.5.1]
-------
fix: fix how we determine the value of active flag within schedule for SAP
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.5.1"
__version__ = "4.6.1"
8 changes: 5 additions & 3 deletions enterprise/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ class EnterpriseCustomerAdmin(DjangoObjectActions, SimpleHistoryAdmin):
'enable_audit_data_reporting', 'enable_learner_portal_offers',
'enable_executive_education_2U_fulfillment',
'enable_career_engagement_network_on_learner_portal',
'career_engagement_network_message', 'enable_pathways', 'enable_programs'),
'career_engagement_network_message', 'enable_pathways', 'enable_programs',
'enable_demo_data_for_analytics_and_lpr'),
'description': ('The following default settings should be the same for '
'the majority of enterprise customers, '
'and are either rarely used, unlikely to be sold, '
Expand Down Expand Up @@ -1079,8 +1080,9 @@ class ChatGPTResponseAdmin(admin.ModelAdmin):
"""

model = models.ChatGPTResponse
list_display = ('uuid', 'enterprise_customer', 'prompt_hash', )
readonly_fields = ('prompt', 'response', 'prompt_hash', )
list_display = ('uuid', 'prompt_type', 'enterprise_customer', 'prompt_hash', 'created', )
readonly_fields = ('prompt_type', 'prompt', 'response', 'prompt_hash', 'created', 'modified', )
list_filter = ('prompt_type', )


@admin.register(models.EnterpriseCustomerSsoConfiguration)
Expand Down
1 change: 1 addition & 0 deletions enterprise/admin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ class Meta:
"career_engagement_network_message",
"enable_pathways",
"enable_programs",
"enable_demo_data_for_analytics_and_lpr",
"enable_analytics_screen",
"enable_portal_reporting_config_screen",
"enable_portal_saml_configuration_screen",
Expand Down
2 changes: 1 addition & 1 deletion enterprise/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def generate_prompt_for_learner_engagement_summary(engagement_data):
'hours': engagement_data['hours'],
'hours_delta': delta_format(current=engagement_data['hours'], prior=engagement_data['hours_prior']),
'passed': engagement_data['passed'],
'passed_delta': delta_format(current=engagement_data['hours'], prior=engagement_data['passed_prior']),
'passed_delta': delta_format(current=engagement_data['passed'], prior=engagement_data['passed_prior']),
}

# If active contract (or unknown).
Expand Down
2 changes: 1 addition & 1 deletion enterprise/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ class Meta:
'enterprise_customer_catalogs', 'reply_to', 'enterprise_notification_banner', 'hide_labor_market_data',
'modified', 'enable_universal_link', 'enable_browse_and_request', 'admin_users',
'enable_career_engagement_network_on_learner_portal', 'career_engagement_network_message',
'enable_pathways', 'enable_programs',
'enable_pathways', 'enable_programs', 'enable_demo_data_for_analytics_and_lpr',
)

identity_providers = EnterpriseCustomerIdentityProviderSerializer(many=True, read_only=True)
Expand Down
8 changes: 6 additions & 2 deletions enterprise/api/v1/views/analytics_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def post(self, request, enterprise_uuid):
learner_engagement_prompt = generate_prompt_for_learner_engagement_summary(prompt_data['learner_engagement'])

return Response(data={
'learner_progress': ChatGPTResponse.get_or_create(learner_progress_prompt, role, enterprise_customer),
'learner_engagement': ChatGPTResponse.get_or_create(learner_engagement_prompt, role, enterprise_customer),
'learner_progress': ChatGPTResponse.get_or_create(
learner_progress_prompt, role, enterprise_customer, ChatGPTResponse.LEARNER_PROGRESS,
),
'learner_engagement': ChatGPTResponse.get_or_create(
learner_engagement_prompt, role, enterprise_customer, ChatGPTResponse.LEARNER_ENGAGEMENT,
),
})
19 changes: 16 additions & 3 deletions enterprise/api/v1/views/enterprise_customer_sso_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,21 @@ def fetch_entity_id_from_metadata_xml(metadata_xml):
raise EntityIdNotFoundError('Could not find entity ID in metadata xml')


def fetch_request_data_from_request(request):
"""
Helper method to fetch the request data dictionary from the request object.
"""
if hasattr(request.data, 'dict'):
return request.data.dict().copy()
return request.data.copy()


class EnterpriseCustomerSsoConfigurationViewSet(viewsets.ModelViewSet):
"""
API views for the ``EnterpriseCustomerSsoConfiguration`` model.
"""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.EnterpriseCustomerSsoConfiguration.all_objects.all()
queryset = models.EnterpriseCustomerSsoConfiguration.objects.all()

serializer_class = serializers.EnterpriseCustomerSsoConfiguration

Expand Down Expand Up @@ -137,6 +146,10 @@ def oauth_orchestration_complete(self, request, configuration_uuid, *args, **kwa
' not been marked as submitted.'
)

# Mark the configuration record as active IFF this the record has never been configured.
if not sso_configuration_record.configured_at:
sso_configuration_record.active = True

sso_configuration_record.configured_at = localized_utcnow()
# Completing the orchestration process for the first time means the configuration record is now configured and
# can be considered active. However, subsequent configurations to update the record should not be reactivated,
Expand Down Expand Up @@ -199,7 +212,7 @@ def list(self, request, *args, **kwargs):
)
def create(self, request, *args, **kwargs):
# Force the enterprise customer to be the one associated with the user
request_data = request.data.dict().copy()
request_data = fetch_request_data_from_request(request)
requesting_user_customer = request_data.get('enterprise_customer')
if requesting_user_customer:
try:
Expand Down Expand Up @@ -264,7 +277,7 @@ def update(self, request, *args, **kwargs):
return Response(status=HTTP_403_FORBIDDEN)

# Parse the request data to see if the metadata url or xml has changed and update the entity id if so
request_data = request.data.dict()
request_data = fetch_request_data_from_request(request)
sso_config_metadata_xml = None
if request_metadata_url := request_data.get('metadata_url'):
sso_config_metadata_url = sso_configuration_record.first().metadata_url
Expand Down
22 changes: 22 additions & 0 deletions enterprise/migrations/0191_auto_20231006_0948.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.21 on 2023-10-06 09:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('enterprise', '0190_auto_20231003_0719'),
]

operations = [
migrations.AlterModelOptions(
name='chatgptresponse',
options={'verbose_name': 'ChatGPT Response', 'verbose_name_plural': 'ChatGPT Responses'},
),
migrations.AddField(
model_name='chatgptresponse',
name='prompt_type',
field=models.CharField(choices=[('learner_progress', 'Learner progress'), ('learner_engagement', 'Learner engagement')], help_text='Prompt type.', max_length=32, null=True),
),
]
23 changes: 23 additions & 0 deletions enterprise/migrations/0192_auto_20231009_1302.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.20 on 2023-10-09 13:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('enterprise', '0191_auto_20231006_0948'),
]

operations = [
migrations.AddField(
model_name='enterprisecustomer',
name='enable_demo_data_for_analytics_and_lpr',
field=models.BooleanField(default=False, help_text='Display Demo data from analyitcs and learner progress report for demo customer.', verbose_name='Enable demo data from analytics and lpr'),
),
migrations.AddField(
model_name='historicalenterprisecustomer',
name='enable_demo_data_for_analytics_and_lpr',
field=models.BooleanField(default=False, help_text='Display Demo data from analyitcs and learner progress report for demo customer.', verbose_name='Enable demo data from analytics and lpr'),
),
]
30 changes: 28 additions & 2 deletions enterprise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,12 @@ class Meta:
help_text=_("Specifies whether the organization should have access to executive education 2U content.")
)

enable_demo_data_for_analytics_and_lpr = models.BooleanField(
verbose_name="Enable demo data from analytics and lpr",
default=False,
help_text=_("Display Demo data from analyitcs and learner progress report for demo customer.")
)

contact_email = models.EmailField(
verbose_name="Customer admin contact email:",
null=True,
Expand Down Expand Up @@ -3655,6 +3661,12 @@ class ChatGPTResponse(TimeStampedModel):
.. no_pii:
"""
LEARNER_PROGRESS = 'learner_progress'
LEARNER_ENGAGEMENT = 'learner_engagement'
PROMPT_TYPES = [
(LEARNER_PROGRESS, 'Learner progress'),
(LEARNER_ENGAGEMENT, 'Learner engagement'),
]
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)

enterprise_customer = models.ForeignKey(
Expand All @@ -3671,6 +3683,12 @@ class ChatGPTResponse(TimeStampedModel):
prompt = models.TextField(help_text=_('ChatGPT prompt.'))
prompt_hash = models.CharField(max_length=32, editable=False)
response = models.TextField(help_text=_('ChatGPT response.'))
prompt_type = models.CharField(choices=PROMPT_TYPES, help_text=_('Prompt type.'), max_length=32, null=True)

class Meta:
app_label = 'enterprise'
verbose_name = _('ChatGPT Response')
verbose_name_plural = _('ChatGPT Responses')

def save(self, *args, **kwargs):
"""
Expand All @@ -3680,7 +3698,7 @@ def save(self, *args, **kwargs):
super().save(*args, **kwargs)

@classmethod
def get_or_create(cls, prompt, role, enterprise_customer):
def get_or_create(cls, prompt, role, enterprise_customer, prompt_type):
"""
Get or create ChatGPT response against given prompt.
Expand All @@ -3691,6 +3709,7 @@ def get_or_create(cls, prompt, role, enterprise_customer):
prompt (str): OpenAI prompt.
role (str): ChatGPT role to assume for the prompt.
enterprise_customer (EnterpriseCustomer): Enterprise customer UUId making the request.
prompt_type (str): Prompt type, e.g. learner_progress or learner_engagement etc.
Returns:
(str): Response against the given prompt.
Expand All @@ -3702,6 +3721,7 @@ def get_or_create(cls, prompt, role, enterprise_customer):
enterprise_customer=enterprise_customer,
prompt=prompt,
response=response,
prompt_type=prompt_type,
)
return response
else:
Expand Down Expand Up @@ -4055,7 +4075,13 @@ def submit_for_configuration(self, updating_existing_record=False):
is_sap = True
else:
for field in self.base_saml_config_fields:
config_data[utils.camelCase(field)] = getattr(self, field)
if field == "active":
if not updating_existing_record:
config_data['enable'] = True
else:
config_data['enable'] = getattr(self, field)
else:
config_data[utils.camelCase(field)] = getattr(self, field)

EnterpriseSSOOrchestratorApiClient().configure_sso_orchestration_record(
config_data=config_data,
Expand Down
2 changes: 1 addition & 1 deletion enterprise/tpa_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def validate_provider_config(enterprise_customer, sso_provider_id):
enterprise_orchestration_config = enterprise_customer.sso_orchestration_records.filter(
active=True
)
if enterprise_orchestration_config.exists():
if enterprise_orchestration_config.exists() and not enterprise_orchestration_config.first().validated_at:
enterprise_orchestration_config.update(validated_at=datetime.now())

# With a successful SSO login, validate the enterprise customer's IDP config if it hasn't already been validated
Expand Down
2 changes: 2 additions & 0 deletions integrated_channels/cornerstone/exporters/content_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ def transform_subjects(self, content_metadata_item):
"""
Return the transformed version of the course subject list or default value if no subject found.
"""
if self.enterprise_configuration.disable_subject_metadata_transmission:
return None
subjects = []
course_subjects = get_subjects_from_content_metadata(content_metadata_item)
CornerstoneGlobalConfiguration = apps.get_model(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.21 on 2023-10-10 16:54

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cornerstone', '0029_alter_historicalcornerstoneenterprisecustomerconfiguration_options'),
]

operations = [
migrations.AddField(
model_name='cornerstoneenterprisecustomerconfiguration',
name='disable_subject_metadata_transmission',
field=models.BooleanField(default=False, help_text='If checked, subjects will not be sent to Cornerstone', verbose_name='Disable Subject Content Metadata Transmission'),
),
migrations.AddField(
model_name='historicalcornerstoneenterprisecustomerconfiguration',
name='disable_subject_metadata_transmission',
field=models.BooleanField(default=False, help_text='If checked, subjects will not be sent to Cornerstone', verbose_name='Disable Subject Content Metadata Transmission'),
),
]
8 changes: 8 additions & 0 deletions integrated_channels/cornerstone/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ class CornerstoneEnterpriseCustomerConfiguration(EnterpriseCustomerPluginConfigu
)
)

disable_subject_metadata_transmission = models.BooleanField(
default=False,
verbose_name="Disable Subject Content Metadata Transmission",
help_text=_(
"If checked, subjects will not be sent to Cornerstone"
)
)

history = HistoricalRecords()

class Meta:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from integrated_channels.exceptions import ClientError
from integrated_channels.integrated_channel.client import IntegratedChannelApiClient
from integrated_channels.integrated_channel.transmitters import Transmitter
from integrated_channels.utils import chunks, generate_formatted_log
from integrated_channels.utils import chunks, encode_binary_data_for_logging, generate_formatted_log

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -153,6 +153,24 @@ def _transmit_action(self, content_metadata_item_map, client_method, action_name
for chunk in islice(chunk_items, transmission_limit):
json_payloads = [item.channel_metadata for item in list(chunk.values())]
serialized_chunk = self._serialize_items(json_payloads)
if self.enterprise_configuration.dry_run_mode_enabled:
enterprise_customer_uuid = self.enterprise_configuration.enterprise_customer.uuid
channel_code = self.enterprise_configuration.channel_code()
for key, item in chunk.items():
payload = item.channel_metadata
serialized_payload = self._serialize_items([payload])
encoded_serialized_payload = encode_binary_data_for_logging(serialized_payload)
LOGGER.info(generate_formatted_log(
channel_code,
enterprise_customer_uuid,
None,
key,
f'dry-run mode content metadata '
f'skipping "{action_name}" action for content metadata transmission '
f'integrated_channel_serialized_payload_base64={encoded_serialized_payload}'
))
continue

response_status_code = None
response_body = None
try:
Expand Down
Loading

0 comments on commit 2c3b179

Please sign in to comment.