musora-content-services 2.21.4 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.22.0](https://github.com/railroadmedia/musora-content-services/compare/v2.21.4...v2.22.0) (2025-07-16)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-837:** update contentAggregator to process parent + children ([#346](https://github.com/railroadmedia/musora-content-services/issues/346)) ([20ffef3](https://github.com/railroadmedia/musora-content-services/commit/20ffef3826a7e56bee4c950d77b2cd57eff1dc08))
11
+
5
12
  ### [2.21.4](https://github.com/railroadmedia/musora-content-services/compare/v2.21.3...v2.21.4) (2025-07-15)
6
13
 
7
14
 
package/link_mcs.sh CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.21.4",
3
+ "version": "2.22.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -179,7 +179,7 @@ export const lessonTypesMapping = {
179
179
  'jam tracks': ['jam-track'],
180
180
  };
181
181
 
182
- export const getNextLessonLessonParentTypes = ['course', 'guided-course', 'pack-bundle'];
182
+ export const getNextLessonLessonParentTypes = ['course', 'guided-course', 'pack', 'pack-bundle', 'song-tutorial'];
183
183
 
184
184
  export const progressTypesMapping = {
185
185
  'lesson': [...singleLessonTypes,...practiceAlongsLessonTypes, ...liveArchivesLessonTypes, ...performancesLessonTypes, ...studentArchivesLessonTypes, ...documentariesLessonTypes, 'live'],
@@ -450,6 +450,7 @@ export let contentTypeConfig = {
450
450
  ),
451
451
  "children": child[]->{
452
452
  "description": ${descriptionField},
453
+ "children": child[]->{"id": railcontent_id},
453
454
  ${getFieldsForContentType()}
454
455
  },
455
456
  ${getFieldsForContentType()}
@@ -175,7 +175,7 @@ export async function getContentRows(brand, pageName, contentRowSlug = null, {
175
175
  } = {}) {
176
176
  const sanityData = await addContextToContent(fetchContentRows, brand, pageName, contentRowSlug, {
177
177
  dataField: 'content',
178
- iterateDataFieldOnEachArrayElement: true,
178
+ dataField_parentIsArray: true,
179
179
  addProgressStatus: true,
180
180
  addProgressPercentage: true,
181
181
  addNextLesson: true
@@ -8,6 +8,51 @@ import {
8
8
  import {isContentLikedByIds} from "./contentLikes"
9
9
  import {fetchLastInteractedChild, fetchLikeCount} from "./railcontent"
10
10
 
11
+ /**
12
+ * Combine sanity data with BE contextual data.
13
+ *
14
+ * Supported dataStructures
15
+ * [{}, {}, {}] <- fetchRelatedLessons || on playback page (side bar)
16
+ * {} <- fetchLessonContent || on playback page (main window)
17
+ * in the examples below, dataField would be set to `children`
18
+ * [{id, children}, {id, children,}] <- getTabData || catalog Page
19
+ * {childen, } <- getPackData || pack index page
20
+ *
21
+ *
22
+ * @param dataPromise - promise or method that provides sanity data
23
+ * @param dataArgs - Arguments to pass to the dataPramise. The final parameter is expected to take the form of the options object
24
+ * @param options - Options has two categories of flags. two for defining the incoming data structure, and the rest of which data to add to the results. Unless otherwise specified the field flags use the format add<X> and add the <X> to the results
25
+ * @param options.dataField - the document field to process. (this is often 'children', 'entity', or 'lessons'
26
+ * @param options.dataField_includeParent - flag: if set with dataField, used to process the same contextual data for the parent object/array as well as the children
27
+ * @param options.addProgressPercentage - add progressPerecentage field
28
+ * @param options.addIsLiked - add isLikedField
29
+ * @param options.addLikeCount - add likeCount field
30
+ * @param options.addProgressStatus - add progressStatus field
31
+ * @param options.addResumeTimeSeconds - add resumeTimeSeconds field
32
+ * @param options.addLastInteractedChild - add lastInteractedChild field. This may be different from nextLesson
33
+ * @param options.addNextLesson - add nextLesson field. For collection type content. each collection has different logic for calculating this data
34
+ *
35
+ * @returns {Promise<{ data: Object[] } | false>} - A promise that resolves to the fetched content data + added data or `false` if no data is found.
36
+ *
37
+ * @example
38
+ * // GetLesson
39
+ * const response = await addContextToContent(fetchLessonContent, id, {
40
+ * addIsLiked: true,
41
+ * addProgressStatus: true,
42
+ * addLikeCount: true,
43
+ * addResumeTimeSeconds: true,
44
+ * })
45
+ *
46
+ * @example - addContextToContent retuns [{id, title, content}], so must be unpacked with dataField, and indicate the dataField_isParentArray
47
+ * const sanityData = await addContextToContent(fetchContentRows, brand, pageName, contentRowSlug, {
48
+ * dataField: 'content',
49
+ * dataField_parentIsArray: true,
50
+ * addProgressStatus: true,
51
+ * addProgressPercentage: true,
52
+ * addNextLesson: true
53
+ * })
54
+ *
55
+ */
11
56
 
12
57
  export async function addContextToContent(dataPromise, ...dataArgs)
13
58
  {
@@ -16,7 +61,7 @@ export async function addContextToContent(dataPromise, ...dataArgs)
16
61
 
17
62
  const {
18
63
  dataField = null,
19
- iterateDataFieldOnEachArrayElement = false,
64
+ dataField_includeParent = false,
20
65
  addProgressPercentage = false,
21
66
  addIsLiked = false,
22
67
  addLikeCount = false,
@@ -24,30 +69,14 @@ export async function addContextToContent(dataPromise, ...dataArgs)
24
69
  addResumeTimeSeconds = false,
25
70
  addLastInteractedChild = false,
26
71
  addNextLesson = false,
27
- addLastInteractedParent = false,
28
72
  } = options
29
73
 
30
74
  const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
31
75
 
32
- const data = await dataPromise(...dataParam)
76
+ let data = await dataPromise(...dataParam)
33
77
  if(!data) return false
34
-
35
- let items = []
36
-
37
- if (dataField && (data?.[dataField] || iterateDataFieldOnEachArrayElement)) {
38
- if (iterateDataFieldOnEachArrayElement && Array.isArray(data)) {
39
- for(const parent of data) {
40
- items = [...items, ...parent[dataField]]
41
- }
42
- } else {
43
- items = data[dataField]
44
- }
45
- } else if (Array.isArray(data)) {
46
- items = data;
47
- } else if (data?.id) {
48
- items = [data]
49
- }
50
-
78
+ const isDataAnArray = Array.isArray(data)
79
+ const items = extractItemsFromData(data, dataField, isDataAnArray, dataField_includeParent)
51
80
  const ids = items.map(item => item?.id).filter(Boolean)
52
81
 
53
82
  if(ids.length === 0) return false
@@ -58,7 +87,7 @@ export async function addContextToContent(dataPromise, ...dataArgs)
58
87
  addIsLiked ? isContentLikedByIds(ids) : Promise.resolve(null),
59
88
  addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids) : Promise.resolve(null),
60
89
  addLastInteractedChild ? fetchLastInteractedChild(ids) : Promise.resolve(null),
61
- (addNextLesson || addLastInteractedParent) ? getNextLesson(items) : Promise.resolve(null),
90
+ addNextLesson ? getNextLesson(items) : Promise.resolve(null),
62
91
  ])
63
92
 
64
93
  const addContext = async (item) => ({
@@ -72,13 +101,41 @@ export async function addContextToContent(dataPromise, ...dataArgs)
72
101
  ...(addNextLesson ? { nextLesson: nextLessonData?.[item.id] } : {}),
73
102
  })
74
103
 
75
- if (addLastInteractedParent) {
76
- const parentId = await getLastInteractedOf(ids);
77
- data['nextLesson'] = nextLessonData[parentId];
104
+ return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
105
+ }
106
+
107
+ function extractItemsFromData(data, dataField, isParentArray, includeParent)
108
+ {
109
+ let items = []
110
+ if (dataField) {
111
+ if (isParentArray) {
112
+ for (const parent of data) {
113
+ items = [...items, ...parent[dataField]]
114
+ }
115
+ } else {
116
+ items = data[dataField]
117
+ }
118
+ if (includeParent) {
119
+ if (isParentArray) {
120
+ for (const parent of data) {
121
+ items = [...items, ...parent]
122
+ }
123
+ } else {
124
+ items = [...items, data]
125
+ }
126
+ }
127
+ } else if (Array.isArray(data)) {
128
+ items = data;
129
+ } else if (data?.id) {
130
+ items = [data]
78
131
  }
132
+ return items
133
+ }
79
134
 
135
+ async function processItems(data, addContext, dataField, isParentArray, includeParent)
136
+ {
80
137
  if (dataField) {
81
- if (iterateDataFieldOnEachArrayElement) {
138
+ if (isParentArray) {
82
139
  for(let parent of data) {
83
140
  parent[dataField] = Array.isArray(parent[dataField])
84
141
  ? await Promise.all(parent[dataField].map(addContext))
@@ -89,11 +146,16 @@ export async function addContextToContent(dataPromise, ...dataArgs)
89
146
  ? await Promise.all(data[dataField].map(addContext))
90
147
  : await addContext(data[dataField])
91
148
  }
149
+ if (includeParent) {
150
+ data = isParentArray
151
+ ? await Promise.all(data.map(addContext))
152
+ : await addContext(data)
153
+ }
92
154
  return data
93
155
  } else {
94
156
  return Array.isArray(data)
95
- ? await Promise.all(data.map(addContext))
96
- : await addContext(data)
157
+ ? await Promise.all(data.map(addContext))
158
+ : await addContext(data)
97
159
  }
98
160
  }
99
161
 
@@ -61,8 +61,6 @@ export async function getNextLesson(data)
61
61
  nextLessonData[content.id] = children[0]
62
62
 
63
63
  } else {
64
- //if content in progress
65
-
66
64
  const childrenStates = await getProgressStateByIds(children)
67
65
 
68
66
  //calculate last_engaged
@@ -77,8 +75,17 @@ export async function getNextLesson(data)
77
75
  nextLessonData[content.id] = findIncompleteLesson(childrenStates, lastInteracted, content.type)
78
76
  }
79
77
 
80
- } else if (content.type === 'guided-course') {
78
+ } else if (content.type === 'guided-course' || content.type === 'song-tutorial') {
81
79
  nextLessonData[content.id] = findIncompleteLesson(childrenStates, lastInteracted, content.type)
80
+ } else if (content.type === 'pack') {
81
+ const packBundles = content.children ?? []
82
+ console.log('pack', content)
83
+ console.log('bundles', packBundles)
84
+ const packBundleProgressData = await getNextLesson(packBundles)
85
+ const parentId = await getLastInteractedOf(packBundles.map(bundle => bundle.id));
86
+ console.log('parentId', parentId)
87
+ console.log('bundleprogressData', packBundleProgressData)
88
+ nextLessonData[content.id] = packBundleProgressData[parentId];
82
89
  }
83
90
  }
84
91
  }
File without changes
@@ -1979,9 +1979,9 @@ export async function fetchSanity(
1979
1979
  return null
1980
1980
  }
1981
1981
 
1982
- if (globalConfig.sanityConfig.debug) {
1983
- console.log('fetchSanity Query:', query)
1984
- }
1982
+ // if (globalConfig.sanityConfig.debug) {
1983
+ // console.log('fetchSanity Query:', query)
1984
+ // }
1985
1985
  const perspective = globalConfig.sanityConfig.perspective ?? 'published'
1986
1986
  const api = globalConfig.sanityConfig.useCachedAPI ? 'apicdn' : 'api'
1987
1987
  const url = `https://${globalConfig.sanityConfig.projectId}.${api}.sanity.io/v${globalConfig.sanityConfig.version}/data/query/${globalConfig.sanityConfig.dataset}?perspective=${perspective}`
@@ -1074,7 +1074,7 @@ async function processContentItem(item) {
1074
1074
  let ctaText = 'Continue';
1075
1075
  if (contentType === 'transcription' || contentType === 'play-along' || contentType === 'jam-track') ctaText = 'Replay Song';
1076
1076
  if (contentType === 'lesson') ctaText = status === 'completed' ? 'Revisit Lesson' : 'Continue';
1077
- if ((contentType === 'song tutorial' || collectionLessonTypes.includes(contentType)) && status === 'completed') ctaText = 'Revisit Lessons' ;
1077
+ if ((contentType === 'song-tutorial' || collectionLessonTypes.includes(contentType)) && status === 'completed') ctaText = 'Revisit Lessons' ;
1078
1078
  if (contentType === 'pack' && status === 'completed') {
1079
1079
  ctaText = 'View Lessons';
1080
1080
  }