From 05938808767ee2faec3ab35b9ca0c208d71df36f Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 14 May 2025 10:38:48 -0400 Subject: [PATCH 01/13] include child resources for programs in response --- learning_resources/serializers.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/learning_resources/serializers.py b/learning_resources/serializers.py index 7ba0c7a821..7fbcc89f83 100644 --- a/learning_resources/serializers.py +++ b/learning_resources/serializers.py @@ -475,6 +475,7 @@ class LearningResourceMetadataDisplaySerializer(serializers.Serializer): languages = serializers.SerializerMethodField( help_text="Languages", allow_null=True ) + levels = serializers.SerializerMethodField(help_text="Levels", allow_null=True) departments = serializers.SerializerMethodField(help_text="Departments") platform = serializers.SerializerMethodField(help_text="Platform", allow_null=True) @@ -889,6 +890,13 @@ class LearningResourceBaseSerializer(serializers.ModelSerializer, WriteableTopic resource_category = serializers.SerializerMethodField() format = serializers.ListField(child=FormatSerializer(), read_only=True) pace = serializers.ListField(child=PaceSerializer(), read_only=True) + children = serializers.SerializerMethodField(allow_null=True) + + def get_children(self, instance): + children = models.LearningResourceRelationship.objects.filter(parent=instance) + return LearningResourceRelationshipChildField( + children, many=True, read_only=True + ).data def get_resource_category(self, instance) -> str: """Return the resource category of the resource""" @@ -940,6 +948,7 @@ class Meta: "resource_prices", "resource_category", "certification", + "children", "certification_type", "professional", "views", @@ -975,13 +984,18 @@ class LearningResourceRelationshipChildField(serializers.ModelSerializer): the LearningResourceSerializer to serialize the child resources """ - def to_representation(self, instance): - """Serializes child as a LearningResource""" # noqa: D401 - return LearningResourceSerializer(instance=instance.child).data + readable_id = serializers.ReadOnlyField(source="child.readable_id") + title = serializers.ReadOnlyField(source="child.title") class Meta: model = models.LearningResourceRelationship - exclude = ("parent", *COMMON_IGNORED_FIELDS) + fields = ( + "child", + "position", + "relation_type", + "title", + "readable_id", + ) class LearningPathResourceSerializer(LearningResourceBaseSerializer): From 221cc7086bf3b3d2758af48c444d83cb46605c71 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 14 May 2025 10:40:49 -0400 Subject: [PATCH 02/13] regenerate spec --- frontends/api/src/generated/v0/api.ts | 42 +++++++++++++++++++++++++++ frontends/api/src/generated/v1/api.ts | 42 +++++++++++++++++++++++++++ openapi/specs/v0.yaml | 35 ++++++++++++++++++++++ openapi/specs/v1.yaml | 35 ++++++++++++++++++++++ 4 files changed, 154 insertions(+) diff --git a/frontends/api/src/generated/v0/api.ts b/frontends/api/src/generated/v0/api.ts index bcf1d4f3b7..3239529950 100644 --- a/frontends/api/src/generated/v0/api.ts +++ b/frontends/api/src/generated/v0/api.ts @@ -1019,6 +1019,12 @@ export interface CourseResource { * @memberof CourseResource */ pace: Array + /** + * + * @type {string} + * @memberof CourseResource + */ + children: string | null /** * * @type {CourseResourceResourceTypeEnum} @@ -2052,6 +2058,12 @@ export interface LearningPathResource { * @memberof LearningPathResource */ pace: Array + /** + * + * @type {string} + * @memberof LearningPathResource + */ + children: string | null /** * * @type {LearningPathResourceResourceTypeEnum} @@ -3758,6 +3770,12 @@ export interface PodcastEpisodeResource { * @memberof PodcastEpisodeResource */ pace: Array + /** + * + * @type {string} + * @memberof PodcastEpisodeResource + */ + children: string | null /** * * @type {PodcastEpisodeResourceResourceTypeEnum} @@ -4059,6 +4077,12 @@ export interface PodcastResource { * @memberof PodcastResource */ pace: Array + /** + * + * @type {string} + * @memberof PodcastResource + */ + children: string | null /** * * @type {PodcastResourceResourceTypeEnum} @@ -4749,6 +4773,12 @@ export interface ProgramResource { * @memberof ProgramResource */ pace: Array + /** + * + * @type {string} + * @memberof ProgramResource + */ + children: string | null /** * * @type {ProgramResourceResourceTypeEnum} @@ -5688,6 +5718,12 @@ export interface VideoPlaylistResource { * @memberof VideoPlaylistResource */ pace: Array + /** + * + * @type {string} + * @memberof VideoPlaylistResource + */ + children: string | null /** * * @type {VideoPlaylistResourceResourceTypeEnum} @@ -5989,6 +6025,12 @@ export interface VideoResource { * @memberof VideoResource */ pace: Array + /** + * + * @type {string} + * @memberof VideoResource + */ + children: string | null /** * * @type {VideoResourceResourceTypeEnum} diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index 81e4a7dde6..176ba05a4f 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -745,6 +745,12 @@ export interface CourseResource { * @memberof CourseResource */ pace: Array + /** + * + * @type {string} + * @memberof CourseResource + */ + children: string | null /** * * @type {CourseResourceResourceTypeEnum} @@ -1623,6 +1629,12 @@ export interface LearningPathResource { * @memberof LearningPathResource */ pace: Array + /** + * + * @type {string} + * @memberof LearningPathResource + */ + children: string | null /** * * @type {LearningPathResourceResourceTypeEnum} @@ -4877,6 +4889,12 @@ export interface PodcastEpisodeResource { * @memberof PodcastEpisodeResource */ pace: Array + /** + * + * @type {string} + * @memberof PodcastEpisodeResource + */ + children: string | null /** * * @type {PodcastEpisodeResourceResourceTypeEnum} @@ -5355,6 +5373,12 @@ export interface PodcastResource { * @memberof PodcastResource */ pace: Array + /** + * + * @type {string} + * @memberof PodcastResource + */ + children: string | null /** * * @type {PodcastResourceResourceTypeEnum} @@ -6065,6 +6089,12 @@ export interface ProgramResource { * @memberof ProgramResource */ pace: Array + /** + * + * @type {string} + * @memberof ProgramResource + */ + children: string | null /** * * @type {ProgramResourceResourceTypeEnum} @@ -7022,6 +7052,12 @@ export interface VideoPlaylistResource { * @memberof VideoPlaylistResource */ pace: Array + /** + * + * @type {string} + * @memberof VideoPlaylistResource + */ + children: string | null /** * * @type {VideoPlaylistResourceResourceTypeEnum} @@ -7488,6 +7524,12 @@ export interface VideoResource { * @memberof VideoResource */ pace: Array + /** + * + * @type {string} + * @memberof VideoResource + */ + children: string | null /** * * @type {VideoResourceResourceTypeEnum} diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index 8e549aa2f4..9c052d4a24 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -2067,6 +2067,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/CourseResourceResourceTypeEnum' @@ -2166,6 +2170,7 @@ components: required: - certification - certification_type + - children - course - course_feature - delivery @@ -2708,6 +2713,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/LearningPathResourceResourceTypeEnum' @@ -2806,6 +2815,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -4001,6 +4011,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/PodcastEpisodeResourceResourceTypeEnum' @@ -4100,6 +4114,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -4273,6 +4288,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/PodcastResourceResourceTypeEnum' @@ -4372,6 +4391,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -4814,6 +4834,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/ProgramResourceResourceTypeEnum' @@ -4913,6 +4937,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -5497,6 +5522,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/VideoPlaylistResourceResourceTypeEnum' @@ -5596,6 +5625,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -5769,6 +5799,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/VideoResourceResourceTypeEnum' @@ -5874,6 +5908,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index fa250ae005..5836f464a2 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -9334,6 +9334,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/CourseResourceResourceTypeEnum' @@ -9433,6 +9437,7 @@ components: required: - certification - certification_type + - children - course - course_feature - delivery @@ -9892,6 +9897,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/LearningPathResourceResourceTypeEnum' @@ -9990,6 +9999,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -12410,6 +12420,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/PodcastEpisodeResourceResourceTypeEnum' @@ -12509,6 +12523,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -12808,6 +12823,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/PodcastResourceResourceTypeEnum' @@ -12907,6 +12926,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -13344,6 +13364,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/ProgramResourceResourceTypeEnum' @@ -13443,6 +13467,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -14014,6 +14039,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/VideoPlaylistResourceResourceTypeEnum' @@ -14113,6 +14142,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments @@ -14398,6 +14428,10 @@ components: - code - name readOnly: true + children: + type: string + readOnly: true + nullable: true resource_type: allOf: - $ref: '#/components/schemas/VideoResourceResourceTypeEnum' @@ -14503,6 +14537,7 @@ components: required: - certification - certification_type + - children - course_feature - delivery - departments From 3f58edf1ba84d9c658d75541471bfad52ac62a23 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 14 May 2025 14:44:28 -0400 Subject: [PATCH 03/13] adding type annotation --- frontends/api/src/generated/v0/api.ts | 101 ++++++++++++++++++++++---- frontends/api/src/generated/v1/api.ts | 66 +++++++++++++---- learning_resources/serializers.py | 41 ++++++----- openapi/specs/v0.yaml | 76 +++++++++++++++---- openapi/specs/v1.yaml | 59 +++++++++++---- 5 files changed, 267 insertions(+), 76 deletions(-) diff --git a/frontends/api/src/generated/v0/api.ts b/frontends/api/src/generated/v0/api.ts index 3239529950..c1bfc0a52f 100644 --- a/frontends/api/src/generated/v0/api.ts +++ b/frontends/api/src/generated/v0/api.ts @@ -1021,10 +1021,10 @@ export interface CourseResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof CourseResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {CourseResourceResourceTypeEnum} @@ -2060,10 +2060,10 @@ export interface LearningPathResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof LearningPathResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {LearningPathResourceResourceTypeEnum} @@ -2579,6 +2579,44 @@ export interface LearningResourcePrice { */ currency: string } +/** + * Serializer field for the LearningResourceRelationship model that uses the LearningResourceSerializer to serialize the child resources + * @export + * @interface LearningResourceRelationshipChildField + */ +export interface LearningResourceRelationshipChildField { + /** + * + * @type {number} + * @memberof LearningResourceRelationshipChildField + */ + child: number + /** + * + * @type {number} + * @memberof LearningResourceRelationshipChildField + */ + position?: number + /** + * + * @type {RelationTypeEnum} + * @memberof LearningResourceRelationshipChildField + */ + relation_type?: RelationTypeEnum + /** + * + * @type {string} + * @memberof LearningResourceRelationshipChildField + */ + title: string + /** + * + * @type {string} + * @memberof LearningResourceRelationshipChildField + */ + readable_id: string +} + /** * Serializer for the LearningResourceRun model * @export @@ -3772,10 +3810,10 @@ export interface PodcastEpisodeResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof PodcastEpisodeResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {PodcastEpisodeResourceResourceTypeEnum} @@ -4079,10 +4117,10 @@ export interface PodcastResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof PodcastResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {PodcastResourceResourceTypeEnum} @@ -4775,10 +4813,10 @@ export interface ProgramResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof ProgramResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {ProgramResourceResourceTypeEnum} @@ -4948,6 +4986,41 @@ export const ProgramResourceResourceTypeEnum = { export type ProgramResourceResourceTypeEnum = (typeof ProgramResourceResourceTypeEnum)[keyof typeof ProgramResourceResourceTypeEnum] +/** + * * `PROGRAM_COURSES` - Program Courses * `LEARNING_PATH_ITEMS` - Learning Path Items * `PODCAST_EPISODES` - Podcast Episodes * `PLAYLIST_VIDEOS` - Playlist Videos + * @export + * @enum {string} + */ + +export const RelationTypeEnumDescriptions = { + PROGRAM_COURSES: "Program Courses", + LEARNING_PATH_ITEMS: "Learning Path Items", + PODCAST_EPISODES: "Podcast Episodes", + PLAYLIST_VIDEOS: "Playlist Videos", +} as const + +export const RelationTypeEnum = { + /** + * Program Courses + */ + ProgramCourses: "PROGRAM_COURSES", + /** + * Learning Path Items + */ + LearningPathItems: "LEARNING_PATH_ITEMS", + /** + * Podcast Episodes + */ + PodcastEpisodes: "PODCAST_EPISODES", + /** + * Playlist Videos + */ + PlaylistVideos: "PLAYLIST_VIDEOS", +} as const + +export type RelationTypeEnum = + (typeof RelationTypeEnum)[keyof typeof RelationTypeEnum] + /** * * `news` - news * `events` - events * @export @@ -5720,10 +5793,10 @@ export interface VideoPlaylistResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof VideoPlaylistResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {VideoPlaylistResourceResourceTypeEnum} @@ -6027,10 +6100,10 @@ export interface VideoResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof VideoResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {VideoResourceResourceTypeEnum} diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index 176ba05a4f..8a95c9813b 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -747,10 +747,10 @@ export interface CourseResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof CourseResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {CourseResourceResourceTypeEnum} @@ -1631,10 +1631,10 @@ export interface LearningPathResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof LearningPathResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {LearningPathResourceResourceTypeEnum} @@ -2644,6 +2644,44 @@ export interface LearningResourceRelationship { child: number } +/** + * Serializer field for the LearningResourceRelationship model that uses the LearningResourceSerializer to serialize the child resources + * @export + * @interface LearningResourceRelationshipChildField + */ +export interface LearningResourceRelationshipChildField { + /** + * + * @type {number} + * @memberof LearningResourceRelationshipChildField + */ + child: number + /** + * + * @type {number} + * @memberof LearningResourceRelationshipChildField + */ + position?: number + /** + * + * @type {RelationTypeEnum} + * @memberof LearningResourceRelationshipChildField + */ + relation_type?: RelationTypeEnum + /** + * + * @type {string} + * @memberof LearningResourceRelationshipChildField + */ + title: string + /** + * + * @type {string} + * @memberof LearningResourceRelationshipChildField + */ + readable_id: string +} + /** * @type LearningResourceRequest * @export @@ -4891,10 +4929,10 @@ export interface PodcastEpisodeResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof PodcastEpisodeResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {PodcastEpisodeResourceResourceTypeEnum} @@ -5375,10 +5413,10 @@ export interface PodcastResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof PodcastResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {PodcastResourceResourceTypeEnum} @@ -6091,10 +6129,10 @@ export interface ProgramResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof ProgramResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {ProgramResourceResourceTypeEnum} @@ -7054,10 +7092,10 @@ export interface VideoPlaylistResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof VideoPlaylistResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {VideoPlaylistResourceResourceTypeEnum} @@ -7526,10 +7564,10 @@ export interface VideoResource { pace: Array /** * - * @type {string} + * @type {LearningResourceRelationshipChildField} * @memberof VideoResource */ - children: string | null + children: LearningResourceRelationshipChildField | null /** * * @type {VideoResourceResourceTypeEnum} diff --git a/learning_resources/serializers.py b/learning_resources/serializers.py index 7fbcc89f83..d531705cf0 100644 --- a/learning_resources/serializers.py +++ b/learning_resources/serializers.py @@ -857,6 +857,26 @@ def render_chunks(self): ] +class LearningResourceRelationshipChildField(serializers.ModelSerializer): + """ + Serializer field for the LearningResourceRelationship model that uses + the LearningResourceSerializer to serialize the child resources + """ + + readable_id = serializers.ReadOnlyField(source="child.readable_id") + title = serializers.ReadOnlyField(source="child.title") + + class Meta: + model = models.LearningResourceRelationship + fields = ( + "child", + "position", + "relation_type", + "title", + "readable_id", + ) + + class LearningResourceBaseSerializer(serializers.ModelSerializer, WriteableTopicsMixin): """Serializer for LearningResource, minus program""" @@ -892,6 +912,7 @@ class LearningResourceBaseSerializer(serializers.ModelSerializer, WriteableTopic pace = serializers.ListField(child=PaceSerializer(), read_only=True) children = serializers.SerializerMethodField(allow_null=True) + @extend_schema_field(LearningResourceRelationshipChildField(allow_null=True)) def get_children(self, instance): children = models.LearningResourceRelationship.objects.filter(parent=instance) return LearningResourceRelationshipChildField( @@ -978,26 +999,6 @@ class CourseResourceSerializer(LearningResourceBaseSerializer): course = CourseSerializer(read_only=True) -class LearningResourceRelationshipChildField(serializers.ModelSerializer): - """ - Serializer field for the LearningResourceRelationship model that uses - the LearningResourceSerializer to serialize the child resources - """ - - readable_id = serializers.ReadOnlyField(source="child.readable_id") - title = serializers.ReadOnlyField(source="child.title") - - class Meta: - model = models.LearningResourceRelationship - fields = ( - "child", - "position", - "relation_type", - "title", - "readable_id", - ) - - class LearningPathResourceSerializer(LearningResourceBaseSerializer): """CRUD serializer for LearningPath resources""" diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index 9c052d4a24..dab79b1502 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -2068,9 +2068,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/CourseResourceResourceTypeEnum' @@ -2714,9 +2715,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/LearningPathResourceResourceTypeEnum' @@ -3107,6 +3109,30 @@ components: required: - amount - currency + LearningResourceRelationshipChildField: + type: object + description: |- + Serializer field for the LearningResourceRelationship model that uses + the LearningResourceSerializer to serialize the child resources + properties: + child: + type: integer + position: + type: integer + maximum: 2147483647 + minimum: 0 + relation_type: + $ref: '#/components/schemas/RelationTypeEnum' + title: + type: string + readOnly: true + readable_id: + type: string + readOnly: true + required: + - child + - readable_id + - title LearningResourceRun: type: object description: Serializer for the LearningResourceRun model @@ -4012,9 +4038,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/PodcastEpisodeResourceResourceTypeEnum' @@ -4289,9 +4316,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/PodcastResourceResourceTypeEnum' @@ -4835,9 +4863,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/ProgramResourceResourceTypeEnum' @@ -4965,6 +4994,23 @@ components: type: string enum: - program + RelationTypeEnum: + enum: + - PROGRAM_COURSES + - LEARNING_PATH_ITEMS + - PODCAST_EPISODES + - PLAYLIST_VIDEOS + type: string + description: |- + * `PROGRAM_COURSES` - Program Courses + * `LEARNING_PATH_ITEMS` - Learning Path Items + * `PODCAST_EPISODES` - Podcast Episodes + * `PLAYLIST_VIDEOS` - Playlist Videos + x-enum-descriptions: + - Program Courses + - Learning Path Items + - Podcast Episodes + - Playlist Videos ResourceTypeEnum: enum: - news @@ -5523,9 +5569,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/VideoPlaylistResourceResourceTypeEnum' @@ -5800,9 +5847,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/VideoResourceResourceTypeEnum' diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index 5836f464a2..1cd9bee89c 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -9335,9 +9335,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/CourseResourceResourceTypeEnum' @@ -9898,9 +9899,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/LearningPathResourceResourceTypeEnum' @@ -10674,6 +10676,30 @@ components: - id - parent - resource + LearningResourceRelationshipChildField: + type: object + description: |- + Serializer field for the LearningResourceRelationship model that uses + the LearningResourceSerializer to serialize the child resources + properties: + child: + type: integer + position: + type: integer + maximum: 2147483647 + minimum: 0 + relation_type: + $ref: '#/components/schemas/RelationTypeEnum' + title: + type: string + readOnly: true + readable_id: + type: string + readOnly: true + required: + - child + - readable_id + - title LearningResourceRequest: oneOf: - $ref: '#/components/schemas/ProgramResourceRequest' @@ -12421,9 +12447,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/PodcastEpisodeResourceResourceTypeEnum' @@ -12824,9 +12851,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/PodcastResourceResourceTypeEnum' @@ -13365,9 +13393,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/ProgramResourceResourceTypeEnum' @@ -14040,9 +14069,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/VideoPlaylistResourceResourceTypeEnum' @@ -14429,9 +14459,10 @@ components: - name readOnly: true children: - type: string - readOnly: true + allOf: + - $ref: '#/components/schemas/LearningResourceRelationshipChildField' nullable: true + readOnly: true resource_type: allOf: - $ref: '#/components/schemas/VideoResourceResourceTypeEnum' From 55afb05b8d04fed6a8c393c8486e14a9e6466957 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 14 May 2025 15:03:34 -0400 Subject: [PATCH 04/13] fixing test --- learning_resources/serializers.py | 3 +-- learning_resources/serializers_test.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/learning_resources/serializers.py b/learning_resources/serializers.py index d531705cf0..176a747ee4 100644 --- a/learning_resources/serializers.py +++ b/learning_resources/serializers.py @@ -914,9 +914,8 @@ class LearningResourceBaseSerializer(serializers.ModelSerializer, WriteableTopic @extend_schema_field(LearningResourceRelationshipChildField(allow_null=True)) def get_children(self, instance): - children = models.LearningResourceRelationship.objects.filter(parent=instance) return LearningResourceRelationshipChildField( - children, many=True, read_only=True + instance.children.allO(), many=True, read_only=True ).data def get_resource_category(self, instance) -> str: diff --git a/learning_resources/serializers_test.py b/learning_resources/serializers_test.py index 965ff8f3e4..06c306add1 100644 --- a/learning_resources/serializers_test.py +++ b/learning_resources/serializers_test.py @@ -218,6 +218,9 @@ def test_learning_resource_serializer( # noqa: PLR0913 "languages": resource.languages, "last_modified": drf_datetime(resource.last_modified), "learning_path_parents": [], + "children": serializers.LearningResourceRelationshipChildField( + resource.children.all(), many=True + ).data, "offered_by": serializers.LearningResourceOfferorSerializer( instance=resource.offered_by ).data, From 88ee0d1e7d968ed4b233fd6cfc4d3e902a14f7d6 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 14 May 2025 15:51:47 -0400 Subject: [PATCH 05/13] fix test --- learning_resources/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learning_resources/serializers.py b/learning_resources/serializers.py index 176a747ee4..7ef54f805e 100644 --- a/learning_resources/serializers.py +++ b/learning_resources/serializers.py @@ -915,7 +915,7 @@ class LearningResourceBaseSerializer(serializers.ModelSerializer, WriteableTopic @extend_schema_field(LearningResourceRelationshipChildField(allow_null=True)) def get_children(self, instance): return LearningResourceRelationshipChildField( - instance.children.allO(), many=True, read_only=True + instance.children.all(), many=True, read_only=True ).data def get_resource_category(self, instance) -> str: From d48d127bb4c967c397e38f7e83cc52c5f70657a0 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 21 May 2025 09:29:15 -0400 Subject: [PATCH 06/13] adding child resources to chat request --- .../AiChatSyllabusSlideDown.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx index 0817f4df0d..eec162a495 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx @@ -195,11 +195,19 @@ const AiChatSyllabusSlideDown = ({ }, credentials: "include", }, - transformBody: (messages) => ({ - collection_name: "content_files", - message: messages[messages.length - 1].content, - course_id: resource.readable_id, - }), + transformBody: (messages) => { + const params = { + collection_name: "content_files", + message: messages[messages.length - 1].content, + course_id: resource.readable_id, + } + if (resource.children) { + params["related_courses"] = resource.children.map( + (child) => child.readable_id, + ) + } + return params + }, }} /> From 21476c10c7b549b9e8f772973b6ead6a478c139c Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 21 May 2025 09:31:15 -0400 Subject: [PATCH 07/13] fixing test --- learning_resources/views_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learning_resources/views_test.py b/learning_resources/views_test.py index a619011d22..128d541069 100644 --- a/learning_resources/views_test.py +++ b/learning_resources/views_test.py @@ -169,7 +169,7 @@ def test_program_endpoint(client, url, params): def test_program_detail_endpoint(client, django_assert_num_queries, url): """Test program endpoint""" program = ProgramFactory.create() - with django_assert_num_queries(17): + with django_assert_num_queries(20): resp = client.get(reverse(url, args=[program.learning_resource.id])) assert resp.data.get("title") == program.learning_resource.title assert resp.data.get("resource_type") == LearningResourceType.program.name From bd3027e9b269e1be7a7dc9c30396229673889f27 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 21 May 2025 09:35:49 -0400 Subject: [PATCH 08/13] fixing lint issue --- .../AiChatSyllabusSlideDown.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx index eec162a495..02c3d7d275 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx @@ -111,7 +111,12 @@ const STARTERS: AiChatProps["conversationStarters"] = [ { content: "What are the prerequisites for this course?" }, { content: "How will this course be graded?" }, ] - +type ChatParams = { + collection_name: string + message: string + course_id: string + related_courses?: string[] +} export const AiChatSyllabusOpener = ({ open, className, @@ -196,14 +201,14 @@ const AiChatSyllabusSlideDown = ({ credentials: "include", }, transformBody: (messages) => { - const params = { + const params: ChatParams = { collection_name: "content_files", message: messages[messages.length - 1].content, course_id: resource.readable_id, } - if (resource.children) { - params["related_courses"] = resource.children.map( - (child) => child.readable_id, + if (Array.isArray(resource.children)) { + params.related_courses = resource.children.map( + (child: { readable_id: string }) => child.readable_id, ) } return params From 613604023c1e4efeee451cf6ca9d538ec51e5eac Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Wed, 21 May 2025 10:10:35 -0400 Subject: [PATCH 09/13] fixing test --- learning_resources/views_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learning_resources/views_test.py b/learning_resources/views_test.py index 128d541069..ff9fe84431 100644 --- a/learning_resources/views_test.py +++ b/learning_resources/views_test.py @@ -169,7 +169,7 @@ def test_program_endpoint(client, url, params): def test_program_detail_endpoint(client, django_assert_num_queries, url): """Test program endpoint""" program = ProgramFactory.create() - with django_assert_num_queries(20): + with django_assert_num_queries(18 + program.learning_resource.children.count()): resp = client.get(reverse(url, args=[program.learning_resource.id])) assert resp.data.get("title") == program.learning_resource.title assert resp.data.get("resource_type") == LearningResourceType.program.name From ea7202a1195c2d4d2f63798052caedd789d973fb Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Fri, 30 May 2025 14:12:59 -0400 Subject: [PATCH 10/13] add syllabus chat to programs --- .../LearningResourceExpanded/LearningResourceExpanded.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx index 3abf192fb2..dc9757da6c 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx @@ -145,7 +145,8 @@ const LearningResourceExpanded: React.FC = ({ const chatEnabled = useFeatureFlagEnabled(FeatureFlags.LrDrawerChatbot) && - resource?.resource_type === ResourceTypeEnum.Course + (resource?.resource_type === ResourceTypeEnum.Course || + resource?.resource_type === ResourceTypeEnum.Program) useEffect(() => { // If URL indicates syllabus open, but it's not enabled, update URL From 95f274b0e30b2f3ebc91153f7bc806bb55989d95 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Fri, 30 May 2025 14:20:57 -0400 Subject: [PATCH 11/13] fixing test --- .../LearningResourceExpanded/LearningResourceExpanded.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx index 72ddd6af2c..cebefcbfd2 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx @@ -372,7 +372,8 @@ describe.each([true, false])( name: "Ask TIM about this course", }) const shouldBeVisible = - enabled && resourceType === ResourceTypeEnum.Course + (enabled && resourceType === ResourceTypeEnum.Course) || + resourceType === ResourceTypeEnum.Program expect(!!chatButton).toBe(shouldBeVisible) }, ) From 26bbb63029d3be3a2fd806f176456818de931768 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Fri, 30 May 2025 14:31:51 -0400 Subject: [PATCH 12/13] fix test --- .../LearningResourceExpanded.test.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx index cebefcbfd2..f0b02be60b 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx @@ -372,8 +372,10 @@ describe.each([true, false])( name: "Ask TIM about this course", }) const shouldBeVisible = - (enabled && resourceType === ResourceTypeEnum.Course) || - resourceType === ResourceTypeEnum.Program + enabled && + (resourceType === ResourceTypeEnum.Course || + resourceType === ResourceTypeEnum.Program) + expect(!!chatButton).toBe(shouldBeVisible) }, ) From d3bfb6c7ec831ca3cc02fe17cef1e20e79f28df2 Mon Sep 17 00:00:00 2001 From: shankar ambady Date: Fri, 30 May 2025 14:40:10 -0400 Subject: [PATCH 13/13] fix test --- .../LearningResourceDrawer/LearningResourceDrawer.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx index 1d8f5d88c1..ed3bd49039 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx @@ -263,8 +263,8 @@ describe("LearningResourceDrawer", () => { mockedUseFeatureFlagEnabled.mockReturnValue(true) const { resource } = setupApis({ resource: { - // Chat is only enabled for courses; NOT enabled here - resource_type: ResourceTypeEnum.Program, + // Chat is only enabled for courses and programs; NOT enabled here + resource_type: ResourceTypeEnum.Podcast, }, }) const { location } = renderWithProviders(, {