Skip to content

Commit

Permalink
Merge pull request #1983 from openedx/ENT-8018/create-api-log-model
Browse files Browse the repository at this point in the history
ENT-8018 - feat: create integrated channel API request log table
  • Loading branch information
hamzawaleed01 authored Jan 15, 2024
2 parents a8c4a60 + a507c96 commit 2e94138
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Change Log
Unreleased
----------

[4.9.4]
--------

feat: Add model for integrated channel API request log table (ENT-8018)

[4.9.3]
--------

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.9.3"
__version__ = "4.9.4"
2 changes: 1 addition & 1 deletion enterprise/api_client/enterprise_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def enterprise_contains_content_items(self, enterprise_uuid, content_ids):
return response.json()['contains_content_items']

@UserAPIClient.refresh_token
def get_content_metadata_content_identifier(self, enterprise_uuid, content_id): # pylint: disable=inconsistent-return-statements
def get_content_metadata_content_identifier(self, enterprise_uuid, content_id):
"""
Return all content metadata contained in the catalogs associated with the
given EnterpriseCustomer and content_id.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.23 on 2024-01-15 07:54

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('integrated_channel', '0030_integratedchannelapirequestlogs'),
('cornerstone', '0030_auto_20231010_1654'),
]

operations = [
migrations.CreateModel(
name='CornerstoneAPIRequestLogs',
fields=[
('integratedchannelapirequestlogs_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='integrated_channel.integratedchannelapirequestlogs')),
('user_agent', models.CharField(max_length=255)),
('user_ip', models.GenericIPAddressField(blank=True, null=True)),
],
bases=('integrated_channel.integratedchannelapirequestlogs',),
),
]
34 changes: 34 additions & 0 deletions integrated_channels/cornerstone/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from integrated_channels.cornerstone.transmitters.learner_data import CornerstoneLearnerTransmitter
from integrated_channels.integrated_channel.models import (
EnterpriseCustomerPluginConfiguration,
IntegratedChannelAPIRequestLogs,
LearnerDataTransmissionAudit,
)
from integrated_channels.utils import is_valid_url
Expand Down Expand Up @@ -318,3 +319,36 @@ class CornerstoneCourseKey(models.Model):

class Meta:
app_label = 'cornerstone'


class CornerstoneAPIRequestLogs(IntegratedChannelAPIRequestLogs):
"""
A model to track basic information about every API call we make from the integrated channels.
"""
user_agent = models.CharField(max_length=255)
user_ip = models.GenericIPAddressField(blank=True, null=True)

class Meta:
app_label = 'cornerstone'

def __str__(self):
"""
Return a human-readable string representation of the object.
"""
return (
f'<CornerstoneAPIRequestLogs {self.id}'
f' for enterprise customer {self.enterprise_customer}, '
f', enterprise_customer_configuration_id: {self.enterprise_customer_configuration_id}>'
f', endpoint: {self.endpoint}'
f', time_taken: {self.time_taken}'
f', user_agent: {self.user_agent}'
f', user_ip: {self.user_ip}'
f', api_record.body: {self.api_record.body}'
f', api_record.status_code: {self.api_record.status_code}'
)

def __repr__(self):
"""
Return uniquely identifying string representation.
"""
return self.__str__()
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 3.2.23 on 2024-01-12 07:24

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields


class Migration(migrations.Migration):

dependencies = [
('enterprise', '0197_auto_20231130_2239'),
('integrated_channel', '0029_genericenterprisecustomerpluginconfiguration_show_course_price'),
]

operations = [
migrations.CreateModel(
name='IntegratedChannelAPIRequestLogs',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('enterprise_customer_configuration_id', models.IntegerField(help_text='ID from the EnterpriseCustomerConfiguration model')),
('endpoint', models.TextField()),
('payload', models.TextField()),
('time_taken', models.DurationField()),
('api_record', models.OneToOneField(blank=True, help_text='Data pertaining to the transmissions API request response.', null=True, on_delete=django.db.models.deletion.CASCADE, to='integrated_channel.apiresponserecord')),
('enterprise_customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='enterprise.enterprisecustomer')),
],
),
]
44 changes: 44 additions & 0 deletions integrated_channels/integrated_channel/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,3 +879,47 @@ class Meta:
on_delete=models.CASCADE,
)
resolved = models.BooleanField(default=False)


class IntegratedChannelAPIRequestLogs(TimeStampedModel):
"""
A model to track basic information about every API call we make from the integrated channels.
"""
enterprise_customer = models.ForeignKey(
EnterpriseCustomer, on_delete=models.CASCADE)
enterprise_customer_configuration_id = models.IntegerField(
blank=False, null=False, help_text='ID from the EnterpriseCustomerConfiguration model')
endpoint = models.TextField(blank=False, null=False)
payload = models.TextField(blank=False, null=False)
time_taken = models.DurationField(blank=False, null=False)
api_record = models.OneToOneField(
ApiResponseRecord,
blank=True,
null=True,
on_delete=models.CASCADE,
help_text=_(
'Data pertaining to the transmissions API request response.')
)

class Meta:
app_label = 'integrated_channel'

def __str__(self):
"""
Return a human-readable string representation of the object.
"""
return (
f'<IntegratedChannelAPIRequestLog {self.id}'
f' for enterprise customer {self.enterprise_customer}, '
f', enterprise_customer_configuration_id: {self.enterprise_customer_configuration_id}>'
f', endpoint: {self.endpoint}'
f', time_taken: {self.time_taken}'
f', api_record.body: {self.api_record.body}'
f', api_record.status_code: {self.api_record.status_code}'
)

def __repr__(self):
"""
Return uniquely identifying string representation.
"""
return self.__str__()
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from pytest import mark

from enterprise.utils import get_content_metadata_item_id, localized_utcnow
from integrated_channels.integrated_channel.models import ApiResponseRecord, ContentMetadataItemTransmission
from integrated_channels.integrated_channel.models import (
ApiResponseRecord,
ContentMetadataItemTransmission,
IntegratedChannelAPIRequestLogs,
)
from test_utils import factories
from test_utils.fake_catalog_api import FAKE_COURSE_RUN, get_fake_catalog, get_fake_content_metadata
from test_utils.fake_enterprise_api import EnterpriseMockMixin
Expand Down Expand Up @@ -251,3 +255,51 @@ def test_offset_naive_error(self):
first_timestamp = localized_utcnow()
self.config.update_content_synced_at(first_timestamp, True)
assert self.config.last_sync_attempted_at == first_timestamp


@mark.django_db
class TestIntegratedChannelAPIRequestLogs(unittest.TestCase, EnterpriseMockMixin):
"""
Tests for the ``IntegratedChannelAPIRequestLogs`` model.
"""

def setUp(self):
self.enterprise_customer = factories.EnterpriseCustomerFactory()
with mock.patch('enterprise.signals.EnterpriseCatalogApiClient'):
self.enterprise_customer_catalog = factories.EnterpriseCustomerCatalogFactory(
enterprise_customer=self.enterprise_customer,
)
self.pk = 1
self.enterprise_customer_configuration_id = 1
self.endpoint = 'https://example.com/endpoint'
self.payload = "{}"
self.time_taken = 500
api_record = ApiResponseRecord(status_code=200, body='SUCCESS')
api_record.save()
self.api_record = api_record
super().setUp()

def test_content_meta_data_string_representation(self):
"""
Test the string representation of the model.
"""
expected_string = (
f'<IntegratedChannelAPIRequestLog {self.pk}'
f' for enterprise customer {self.enterprise_customer}, '
f', enterprise_customer_configuration_id: {self.enterprise_customer_configuration_id}>'
f', endpoint: {self.endpoint}'
f', time_taken: {self.time_taken}'
f', api_record.body: {self.api_record.body}'
f', api_record.status_code: {self.api_record.status_code}'
)

request_log = IntegratedChannelAPIRequestLogs(
id=1,
enterprise_customer=self.enterprise_customer,
enterprise_customer_configuration_id=self.enterprise_customer_configuration_id,
endpoint=self.endpoint,
payload=self.payload,
time_taken=self.time_taken,
api_record=self.api_record
)
assert expected_string == repr(request_log)
1 change: 1 addition & 0 deletions tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def setUp(self):
"auth_org_id",
"active",
"country",
"integratedchannelapirequestlogs",
"invite_keys",
"hide_course_original_price",
"site",
Expand Down

0 comments on commit 2e94138

Please sign in to comment.