musora-content-services 2.30.3 → 2.30.5

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.
Files changed (144) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/CHANGELOG.md +16 -0
  10. package/README.md +0 -0
  11. package/babel.config.cjs +0 -0
  12. package/docs/ContentOrganization.html +0 -0
  13. package/docs/Gamification.html +0 -0
  14. package/docs/UserManagementSystem.html +0 -0
  15. package/docs/api_types.js.html +0 -0
  16. package/docs/config.js.html +0 -0
  17. package/docs/content-org_content-org.js.html +0 -0
  18. package/docs/content-org_playlists-types.js.html +0 -0
  19. package/docs/content-org_playlists.js.html +0 -0
  20. package/docs/content.js.html +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  26. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  27. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  28. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  36. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  39. package/docs/gamification_awards.js.html +0 -0
  40. package/docs/gamification_gamification.js.html +0 -0
  41. package/docs/gamification_types.js.html +0 -0
  42. package/docs/global.html +0 -0
  43. package/docs/index.html +0 -0
  44. package/docs/module-Awards.html +0 -0
  45. package/docs/module-Config.html +0 -0
  46. package/docs/module-Content-Services-V2.html +0 -0
  47. package/docs/module-Interests.html +0 -0
  48. package/docs/module-Permissions.html +0 -0
  49. package/docs/module-Playlists.html +0 -0
  50. package/docs/module-Railcontent-Services.html +0 -0
  51. package/docs/module-Sanity-Services.html +0 -0
  52. package/docs/module-Sessions.html +0 -0
  53. package/docs/module-UserActivity.html +0 -0
  54. package/docs/module-UserChat.html +0 -0
  55. package/docs/module-UserManagement.html +0 -0
  56. package/docs/module-UserNotifications.html +0 -0
  57. package/docs/module-UserProfile.html +0 -0
  58. package/docs/railcontent.js.html +0 -0
  59. package/docs/sanity.js.html +0 -0
  60. package/docs/scripts/collapse.js +0 -0
  61. package/docs/scripts/commonNav.js +0 -0
  62. package/docs/scripts/linenumber.js +0 -0
  63. package/docs/scripts/nav.js +0 -0
  64. package/docs/scripts/polyfill.js +0 -0
  65. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  66. package/docs/scripts/prettify/lang-css.js +0 -0
  67. package/docs/scripts/prettify/prettify.js +0 -0
  68. package/docs/scripts/search.js +0 -0
  69. package/docs/styles/jsdoc.css +0 -0
  70. package/docs/styles/prettify.css +0 -0
  71. package/docs/userActivity.js.html +0 -0
  72. package/docs/user_chat.js.html +0 -0
  73. package/docs/user_interests.js.html +0 -0
  74. package/docs/user_management.js.html +0 -0
  75. package/docs/user_notifications.js.html +0 -0
  76. package/docs/user_permissions.js.html +0 -0
  77. package/docs/user_profile.js.html +0 -0
  78. package/docs/user_sessions.js.html +0 -0
  79. package/docs/user_types.js.html +0 -0
  80. package/docs/user_user-management-system.js.html +0 -0
  81. package/jest.config.js +0 -0
  82. package/jsdoc.json +0 -0
  83. package/package.json +1 -1
  84. package/src/contentMetaData.js +0 -0
  85. package/src/filterBuilder.js +0 -0
  86. package/src/index.d.ts +0 -0
  87. package/src/index.js +0 -0
  88. package/src/infrastructure/http/HttpClient.ts +0 -0
  89. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  90. package/src/infrastructure/http/index.ts +0 -0
  91. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  92. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  93. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  94. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  95. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  96. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  97. package/src/lib/httpHelper.js +0 -0
  98. package/src/lib/lastUpdated.js +0 -0
  99. package/src/services/api/types.js +0 -0
  100. package/src/services/content-org/content-org.js +0 -0
  101. package/src/services/content-org/guided-courses.ts +0 -0
  102. package/src/services/contentLikes.js +0 -0
  103. package/src/services/dataContext.js +0 -0
  104. package/src/services/dateUtils.js +0 -0
  105. package/src/services/forum.js +0 -0
  106. package/src/services/gamification/gamification.js +0 -0
  107. package/src/services/imageSRCBuilder.js +0 -0
  108. package/src/services/imageSRCVerify.js +0 -0
  109. package/src/services/sanity.js +2 -0
  110. package/src/services/user/account.ts +0 -0
  111. package/src/services/user/chat.js +0 -0
  112. package/src/services/user/interests.js +0 -0
  113. package/src/services/user/management.js +0 -0
  114. package/src/services/user/notifications.js +0 -0
  115. package/src/services/user/permissions.js +0 -0
  116. package/src/services/user/profile.js +0 -0
  117. package/src/services/user/sessions.js +0 -0
  118. package/src/services/user/types.js +0 -0
  119. package/src/services/user/user-management-system.js +0 -0
  120. package/src/services/userActivity.js +191 -143
  121. package/test/HttpClient.test.js +0 -0
  122. package/test/content.test.js +0 -0
  123. package/test/contentLikes.test.js +0 -0
  124. package/test/contentProgress.test.js +0 -0
  125. package/test/dataContext.test.js +0 -0
  126. package/test/forum.test.js +0 -0
  127. package/test/imageSRCBuilder.test.js +0 -0
  128. package/test/imageSRCVerify.test.js +0 -0
  129. package/test/lib/lastUpdated.test.js +0 -0
  130. package/test/live/contentProgressLive.test.js +0 -0
  131. package/test/live/railcontentLive.test.js +0 -0
  132. package/test/localStorageMock.js +0 -0
  133. package/test/log.js +0 -0
  134. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  135. package/test/mockData/mockData_progress_content.json +0 -0
  136. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  137. package/test/mockData/mockData_user_practices.json +0 -0
  138. package/test/notifications.test.js +0 -0
  139. package/test/progressRows.test.js +0 -0
  140. package/test/sanityQueryService.test.js +0 -0
  141. package/test/streakMessage.test.js +0 -0
  142. package/test/user/permissions.test.js +0 -0
  143. package/test/userActivity.test.js +0 -0
  144. package/tools/generate-index.cjs +0 -0
package/.coderabbit.yaml CHANGED
File without changes
package/.editorconfig CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
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.30.5](https://github.com/railroadmedia/musora-content-services/compare/v2.30.4...v2.30.5) (2025-08-06)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **MU2-918:** practice meta data for manual only sessions ([#402](https://github.com/railroadmedia/musora-content-services/issues/402)) ([a911529](https://github.com/railroadmedia/musora-content-services/commit/a9115297f00b6aed5f0ad73714cb860cfd71f331))
11
+ * **MU2-918:** safer .find use ([#405](https://github.com/railroadmedia/musora-content-services/issues/405)) ([ce81469](https://github.com/railroadmedia/musora-content-services/commit/ce814692a7aa46ee814c28ecae1ec404a0c09046))
12
+
13
+ ### [2.30.4](https://github.com/railroadmedia/musora-content-services/compare/v2.30.3...v2.30.4) (2025-08-05)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **MU2-912:** pagination for Incomplete and Completed tabs ([#396](https://github.com/railroadmedia/musora-content-services/issues/396)) ([b66d574](https://github.com/railroadmedia/musora-content-services/commit/b66d57483df4bdd158a985bade3cf63fa3aa65bb))
19
+ * **MU2-916:** Add navigateTo and parentId to practice tracker and recent activity ([#398](https://github.com/railroadmedia/musora-content-services/issues/398)) ([6e43768](https://github.com/railroadmedia/musora-content-services/commit/6e437682f09655cc41966277c967c304b275d0ea))
20
+
5
21
  ### [2.30.3](https://github.com/railroadmedia/musora-content-services/compare/v2.30.2...v2.30.3) (2025-08-04)
6
22
 
7
23
  ### [2.30.2](https://github.com/railroadmedia/musora-content-services/compare/v2.30.1...v2.30.2) (2025-08-04)
package/README.md CHANGED
File without changes
package/babel.config.cjs CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/docs/global.html CHANGED
File without changes
package/docs/index.html CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.30.3",
3
+ "version": "2.30.5",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
File without changes
File without changes
package/src/index.d.ts CHANGED
File without changes
package/src/index.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -2173,10 +2173,12 @@ export async function fetchTabData(
2173
2173
  case 'incomplete':
2174
2174
  progressIds = await getAllStarted();
2175
2175
  sortOrder = null;
2176
+ withoutPagination = true;
2176
2177
  break;
2177
2178
  case 'completed':
2178
2179
  progressIds = await getAllCompleted();
2179
2180
  sortOrder = null;
2181
+ withoutPagination = true;
2180
2182
  break;
2181
2183
  }
2182
2184
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -35,7 +35,7 @@ import { TabResponseType } from '../contentMetaData'
35
35
  import dayjs from 'dayjs'
36
36
  import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
37
37
  import weekOfYear from 'dayjs/plugin/weekOfYear'
38
- import {addContextToContent} from "./contentAggregator.js";
38
+ import { addContextToContent } from './contentAggregator.js'
39
39
 
40
40
  const DATA_KEY_PRACTICES = 'practices'
41
41
  const DATA_KEY_LAST_UPDATED_TIME = 'u'
@@ -407,7 +407,7 @@ export async function restoreUserPractice(id) {
407
407
  const restoredPractice = response.data.find((p) => p.id === id)
408
408
  if (restoredPractice) {
409
409
  await userActivityContext.updateLocal(async function (localContext) {
410
- if (localContext.data[DATA_KEY_PRACTICES][restoredPractice.day]) {
410
+ if (!localContext.data[DATA_KEY_PRACTICES][restoredPractice.day]) {
411
411
  localContext.data[DATA_KEY_PRACTICES][restoredPractice.day] = []
412
412
  }
413
413
  response.data.forEach((restoredPractice) => {
@@ -581,7 +581,21 @@ export async function getPracticeNotes(day) {
581
581
  * .catch(error => console.error("Failed to get recent activity:", error));
582
582
  */
583
583
  export async function getRecentActivity({ page = 1, limit = 5, tabName = null } = {}) {
584
- return await fetchRecentUserActivities({ page, limit, tabName })
584
+ const recentActivityData = await fetchRecentUserActivities({ page, limit, tabName })
585
+ const contentIds = recentActivityData.data.map((p) => p.contentId).filter((id) => id !== null)
586
+ const contents = await addContextToContent(fetchByRailContentIds, contentIds, {
587
+ addNavigateTo: true,
588
+ addNextLesson: true,
589
+ })
590
+ recentActivityData.data = recentActivityData.data.map((practice) => {
591
+ const content = contents?.find((c) => c.id === practice.contentId) || {}
592
+ return {
593
+ ...practice,
594
+ parent_id: content.parent_id || null,
595
+ navigateTo: content.navigateTo,
596
+ }
597
+ })
598
+ return recentActivityData
585
599
  }
586
600
 
587
601
  /**
@@ -839,12 +853,15 @@ export async function calculateLongestStreaks(userId = globalConfig.sessionConfi
839
853
  }
840
854
  }
841
855
 
842
- async function formatPracticeMeta(practices) {
856
+ async function formatPracticeMeta(practices = []) {
843
857
  const contentIds = practices.map((p) => p.content_id).filter((id) => id !== null)
844
- const contents = await fetchByRailContentIds(contentIds)
858
+ const contents = await addContextToContent(fetchByRailContentIds, contentIds, {
859
+ addNavigateTo: true,
860
+ addNextLesson: true,
861
+ })
845
862
 
846
863
  return practices.map((practice) => {
847
- const content = contents.find((c) => c.id === practice.content_id) || {}
864
+ const content = contents && contents.length > 0 ? contents.find((c) => c.id === practice.content_id) : {}
848
865
 
849
866
  return {
850
867
  id: practice.id,
@@ -853,16 +870,18 @@ async function formatPracticeMeta(practices) {
853
870
  thumbnail_url: practice.content_id ? content.thumbnail : practice.thumbnail_url || '',
854
871
  duration: practice.duration_seconds || 0,
855
872
  duration_seconds: practice.duration_seconds || 0,
856
- content_url: content.url || null,
873
+ content_url: content?.url || null,
857
874
  title: practice.content_id ? content.title : practice.title,
858
875
  category_id: practice.category_id,
859
876
  instrument_id: practice.instrument_id,
860
- content_type: getFormattedType(content.type || '', content.brand),
877
+ content_type: getFormattedType(content?.type || '', content?.brand || null),
861
878
  content_id: practice.content_id || null,
862
- content_brand: content.brand || null,
879
+ content_brand: content?.brand || null,
863
880
  created_at: dayjs(practice.created_at),
864
- sanity_type: content.type || null,
865
- content_slug: content.slug || null,
881
+ sanity_type: content?.type || null,
882
+ content_slug: content?.slug || null,
883
+ parent_id: content?.parent_id || null,
884
+ navigateTo: content?.navigateTo || null,
866
885
  }
867
886
  })
868
887
  }
@@ -911,24 +930,31 @@ export async function deleteUserActivity(id) {
911
930
  return await fetchHandler(url, 'DELETE')
912
931
  }
913
932
 
914
- async function extractPinnedItemsAndSortAllItems(userPinnedItem, contentsMap, eligiblePlaylistItems, pinnedGuidedCourse, limit) {
933
+ async function extractPinnedItemsAndSortAllItems(
934
+ userPinnedItem,
935
+ contentsMap,
936
+ eligiblePlaylistItems,
937
+ pinnedGuidedCourse,
938
+ limit
939
+ ) {
915
940
  let pinnedItem = await popPinnedItemFromContentsOrPlaylistMap(
916
941
  userPinnedItem,
917
942
  contentsMap,
918
- eligiblePlaylistItems,
943
+ eligiblePlaylistItems
919
944
  )
920
945
 
921
946
  const guidedCourseID = pinnedGuidedCourse?.content_id
922
- let combined = [];
947
+ let combined = []
923
948
  if (pinnedGuidedCourse) {
924
- const guidedCourseContent = contentsMap.get(guidedCourseID) ?? await addContextToContent(fetchByRailContentId, guidedCourseID, 'guided-course',
925
- {
949
+ const guidedCourseContent =
950
+ contentsMap.get(guidedCourseID) ??
951
+ (await addContextToContent(fetchByRailContentId, guidedCourseID, 'guided-course', {
926
952
  addNextLesson: true,
927
953
  addNavigateTo: true,
928
954
  addProgressStatus: true,
929
955
  addProgressPercentage: true,
930
956
  addProgressTimestamp: true,
931
- })
957
+ }))
932
958
  contentsMap = popContentAndRemoveChildrenFromContentsMap(guidedCourseContent, contentsMap)
933
959
  guidedCourseContent.pinned = true
934
960
  combined.push(guidedCourseContent)
@@ -949,23 +975,26 @@ function generateContentsMap(contents, playlistsContents) {
949
975
  'learning-path-course',
950
976
  'learning-path-level',
951
977
  'guided-course-part',
952
- ]);
953
- const existingShows = new Set();
954
- const contentsMap = new Map();
955
- const childToParentMap = {};
956
- contents.forEach(content => {
978
+ ])
979
+ const existingShows = new Set()
980
+ const contentsMap = new Map()
981
+ const childToParentMap = {}
982
+ contents.forEach((content) => {
957
983
  if (Array.isArray(content.parent_content_data) && content.parent_content_data.length > 0) {
958
- childToParentMap[content.id] = content.parent_content_data[content.parent_content_data.length - 1];
984
+ childToParentMap[content.id] =
985
+ content.parent_content_data[content.parent_content_data.length - 1]
959
986
  }
960
- });
987
+ })
961
988
 
962
- const allRecentTypeSet = new Set(
963
- Object.values(recentTypes).flat()
964
- )
965
- contents.forEach(content => {
989
+ const allRecentTypeSet = new Set(Object.values(recentTypes).flat())
990
+ contents.forEach((content) => {
966
991
  const id = content.id
967
992
  const type = content.type
968
- if (excludedTypes.has(type) || (!allRecentTypeSet.has(type) && !showsLessonTypes.includes(type)) ) return;
993
+ if (
994
+ excludedTypes.has(type) ||
995
+ (!allRecentTypeSet.has(type) && !showsLessonTypes.includes(type))
996
+ )
997
+ return
969
998
  if (!childToParentMap[id]) {
970
999
  // Shows don't have a parent to link them, but need to be handled as if they're a set of children
971
1000
  if (!existingShows.has(type)) {
@@ -983,11 +1012,11 @@ function generateContentsMap(contents, playlistsContents) {
983
1012
  for (const item of playlistsContents) {
984
1013
  const contentId = item.id
985
1014
  contentsMap.delete(contentId)
986
- const parentIds = item.parent_content_data || [];
987
- parentIds.forEach(id => contentsMap.delete(id));
1015
+ const parentIds = item.parent_content_data || []
1016
+ parentIds.forEach((id) => contentsMap.delete(id))
988
1017
  }
989
1018
  }
990
- return contentsMap;
1019
+ return contentsMap
991
1020
  }
992
1021
 
993
1022
  /**
@@ -1004,19 +1033,21 @@ function generateContentsMap(contents, playlistsContents) {
1004
1033
  * .catch(error => console.error(error));
1005
1034
  */
1006
1035
  export async function getProgressRows({ brand = null, limit = 8 } = {}) {
1007
-
1008
1036
  // TODO slice progress to a reasonable number, say 100
1009
- const [recentPlaylists, progressContents, allPinnedGuidedCourse, userPinnedItem ] = await Promise.all([
1010
- fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit}),
1011
- getAllStartedOrCompleted({onlyIds: false, brand: brand }),
1012
- pinnedGuidedCourses(brand),
1013
- getUserPinnedItem(brand),
1014
- ])
1037
+ const [recentPlaylists, progressContents, allPinnedGuidedCourse, userPinnedItem] =
1038
+ await Promise.all([
1039
+ fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
1040
+ getAllStartedOrCompleted({ onlyIds: false, brand: brand }),
1041
+ pinnedGuidedCourses(brand),
1042
+ getUserPinnedItem(brand),
1043
+ ])
1015
1044
  let pinnedGuidedCourse = allPinnedGuidedCourse?.[0] ?? null
1016
1045
 
1017
- const playlists = recentPlaylists?.data || [];
1018
- const eligiblePlaylistItems = await getEligiblePlaylistItems(playlists);
1019
- const playlistEngagedOnContents = eligiblePlaylistItems.map(item => item.playlist.last_engaged_on);
1046
+ const playlists = recentPlaylists?.data || []
1047
+ const eligiblePlaylistItems = await getEligiblePlaylistItems(playlists)
1048
+ const playlistEngagedOnContents = eligiblePlaylistItems.map(
1049
+ (item) => item.playlist.last_engaged_on
1050
+ )
1020
1051
 
1021
1052
  const nonPlaylistContentIds = Object.keys(progressContents)
1022
1053
  if (pinnedGuidedCourse) {
@@ -1025,7 +1056,7 @@ export async function getProgressRows({ brand = null, limit = 8 } = {}) {
1025
1056
  if (userPinnedItem?.progressType === 'content') {
1026
1057
  nonPlaylistContentIds.push(userPinnedItem.id)
1027
1058
  }
1028
- const [ playlistsContents, contents ] = await Promise.all([
1059
+ const [playlistsContents, contents] = await Promise.all([
1029
1060
  addContextToContent(fetchByRailContentIds, playlistEngagedOnContents, 'progress-tracker', {
1030
1061
  addNextLesson: true,
1031
1062
  addNavigateTo: true,
@@ -1039,17 +1070,23 @@ export async function getProgressRows({ brand = null, limit = 8 } = {}) {
1039
1070
  addProgressStatus: true,
1040
1071
  addProgressPercentage: true,
1041
1072
  addProgressTimestamp: true,
1042
- })
1043
- ]);
1044
- const contentsMap = generateContentsMap(contents, playlistsContents);
1045
- let combined = await extractPinnedItemsAndSortAllItems(userPinnedItem, contentsMap, eligiblePlaylistItems, pinnedGuidedCourse, limit);
1073
+ }),
1074
+ ])
1075
+ const contentsMap = generateContentsMap(contents, playlistsContents)
1076
+ let combined = await extractPinnedItemsAndSortAllItems(
1077
+ userPinnedItem,
1078
+ contentsMap,
1079
+ eligiblePlaylistItems,
1080
+ pinnedGuidedCourse,
1081
+ limit
1082
+ )
1046
1083
  const results = await Promise.all(
1047
- combined.slice(0, limit).map(item =>
1048
- item.type === 'playlist'
1049
- ? processPlaylistItem(item)
1050
- : processContentItem(item)
1051
- )
1052
- );
1084
+ combined
1085
+ .slice(0, limit)
1086
+ .map((item) =>
1087
+ item.type === 'playlist' ? processPlaylistItem(item) : processContentItem(item)
1088
+ )
1089
+ )
1053
1090
  console.log('HomePageProgressRows results: remove before merge', results)
1054
1091
  return {
1055
1092
  type: TabResponseType.PROGRESS_ROWS,
@@ -1066,118 +1103,132 @@ async function getUserPinnedItem(brand) {
1066
1103
  }
1067
1104
 
1068
1105
  async function processContentItem(content) {
1069
- const contentType = getFormattedType(content.type, content.brand);
1106
+ const contentType = getFormattedType(content.type, content.brand)
1070
1107
  const isLive = content.isLive ?? false
1071
1108
  let ctaText = getDefaultCTATextForContent(content, contentType)
1072
1109
 
1073
1110
  content.completed_children = await getCompletedChildren(content, contentType)
1074
1111
 
1075
1112
  if (content.type === 'guided-course') {
1076
- const nextLessonPublishedOn = content.children.find(child => child.id === content.navigateTo.id)?.published_on
1113
+ const nextLessonPublishedOn = content.children.find(
1114
+ (child) => child.id === content.navigateTo.id
1115
+ )?.published_on
1077
1116
  let isLocked = new Date(nextLessonPublishedOn) > new Date()
1078
1117
  if (isLocked) {
1079
1118
  content.is_locked = true
1080
- const timeRemaining = getTimeRemainingUntilLocal(nextLessonPublishedOn, {withTotalSeconds: true})
1119
+ const timeRemaining = getTimeRemainingUntilLocal(nextLessonPublishedOn, {
1120
+ withTotalSeconds: true,
1121
+ })
1081
1122
  content.time_remaining_seconds = timeRemaining.totalSeconds
1082
1123
  ctaText = 'Next lesson in ' + timeRemaining.formatted
1083
- } else if (!content.progressStatus || content.progressStatus === 'not-started' ) {
1084
- ctaText = "Start Course"
1124
+ } else if (!content.progressStatus || content.progressStatus === 'not-started') {
1125
+ ctaText = 'Start Course'
1085
1126
  }
1086
1127
  }
1087
1128
 
1088
- if (contentType === 'show'){
1129
+ if (contentType === 'show') {
1089
1130
  const shows = await fetchShows(content.brand, content.type)
1090
- const showIds = shows.map(item => item.id);
1091
- const progressOnItems = await getProgressStateByIds(showIds);
1131
+ const showIds = shows.map((item) => item.id)
1132
+ const progressOnItems = await getProgressStateByIds(showIds)
1092
1133
  const completedShows = content.completed_children
1093
1134
  const progressTimestamp = content.progressTimestamp
1094
1135
  const wasPinned = content.pinned ?? false
1095
1136
  if (content.progressStatus === 'completed') {
1096
1137
  // this could be handled more gracefully if their was a parent content type for shows
1097
- const nextByProgress = findIncompleteLesson(progressOnItems, content.id, content.type);
1098
- content = shows.find(lesson => lesson.id === nextByProgress);
1138
+ const nextByProgress = findIncompleteLesson(progressOnItems, content.id, content.type)
1139
+ content = shows.find((lesson) => lesson.id === nextByProgress)
1099
1140
  content.completed_children = completedShows
1100
1141
  content.progressTimestamp = progressTimestamp
1101
1142
  content.pinned = wasPinned
1102
1143
  }
1103
- content.child_count = shows.length;
1104
- content.progressPercentage = Math.round((completedShows / shows.length) * 100);
1144
+ content.child_count = shows.length
1145
+ content.progressPercentage = Math.round((completedShows / shows.length) * 100)
1105
1146
  if (completedShows === shows.length) {
1106
- ctaText = 'Revisit Show';
1147
+ ctaText = 'Revisit Show'
1107
1148
  }
1108
1149
  }
1109
1150
 
1110
1151
  return {
1111
- id: content.id,
1112
- progressType: 'content',
1113
- header: contentType,
1114
- pinned: content.pinned ?? false,
1115
- content: content,
1116
- body: {
1117
- progressPercent: isLive ? undefined: content.progressPercentage,
1118
- thumbnail: content.thumbnail,
1119
- title: content.title,
1120
- isLive: isLive,
1121
- badge: content.badge ?? null,
1122
- isLocked: content.is_locked ?? false,
1123
- subtitle: !content.child_count || content.lesson_count === 1
1124
- ? (contentType === 'lesson' && isLive === false) ? `${content.progressPercentage}% Complete`: `${content.difficulty_string} ${content.artist_name}`
1125
- : `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`
1152
+ id: content.id,
1153
+ progressType: 'content',
1154
+ header: contentType,
1155
+ pinned: content.pinned ?? false,
1156
+ content: content,
1157
+ body: {
1158
+ progressPercent: isLive ? undefined : content.progressPercentage,
1159
+ thumbnail: content.thumbnail,
1160
+ title: content.title,
1161
+ isLive: isLive,
1162
+ badge: content.badge ?? null,
1163
+ isLocked: content.is_locked ?? false,
1164
+ subtitle:
1165
+ !content.child_count || content.lesson_count === 1
1166
+ ? contentType === 'lesson' && isLive === false
1167
+ ? `${content.progressPercentage}% Complete`
1168
+ : `${content.difficulty_string} • ${content.artist_name}`
1169
+ : `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`,
1126
1170
  },
1127
- cta: {
1128
- text: ctaText,
1171
+ cta: {
1172
+ text: ctaText,
1129
1173
  timeRemainingToUnlockSeconds: content.time_remaining_seconds ?? null,
1130
1174
  action: {
1131
- type: content.type,
1175
+ type: content.type,
1132
1176
  brand: content.brand,
1133
- id: content.id,
1134
- slug: content.slug,
1177
+ id: content.id,
1178
+ slug: content.slug,
1135
1179
  child: content.navigateTo,
1136
- }
1180
+ },
1137
1181
  },
1138
1182
  // *1000 is to match playlists which are saved in millisecond accuracy
1139
- progressTimestamp: content.progressTimestamp * 1000
1140
- };
1183
+ progressTimestamp: content.progressTimestamp * 1000,
1184
+ }
1141
1185
  }
1142
1186
 
1143
- function getDefaultCTATextForContent(content, contentType)
1144
- {
1145
- let ctaText = 'Continue';
1146
- if (content.progressStatus === 'completed')
1147
- {
1148
- if (contentType === songs[content.brand] || contentType === 'play along' || contentType === 'jam track') ctaText = 'Replay Song';
1149
- if (contentType === 'lesson') ctaText = 'Revisit Lesson';
1150
- if (contentType === 'song tutorial' || collectionLessonTypes.includes(contentType)) ctaText = 'Revisit Lessons' ;
1187
+ function getDefaultCTATextForContent(content, contentType) {
1188
+ let ctaText = 'Continue'
1189
+ if (content.progressStatus === 'completed') {
1190
+ if (
1191
+ contentType === songs[content.brand] ||
1192
+ contentType === 'play along' ||
1193
+ contentType === 'jam track'
1194
+ )
1195
+ ctaText = 'Replay Song'
1196
+ if (contentType === 'lesson') ctaText = 'Revisit Lesson'
1197
+ if (contentType === 'song tutorial' || collectionLessonTypes.includes(contentType))
1198
+ ctaText = 'Revisit Lessons'
1151
1199
  if (contentType === 'pack') ctaText = 'View Lessons'
1152
1200
  }
1153
1201
  return ctaText
1154
1202
  }
1155
1203
 
1156
- async function getCompletedChildren(content, contentType)
1157
- {
1204
+ async function getCompletedChildren(content, contentType) {
1158
1205
  let completedChildren = null
1159
1206
  if (contentType === 'show') {
1160
1207
  const shows = await addContextToContent(fetchShows, content.brand, content.type, {
1161
1208
  addProgressStatus: true,
1162
1209
  })
1163
- completedChildren = Object.values(shows).filter(show => show.progressStatus === 'completed').length;
1210
+ completedChildren = Object.values(shows).filter(
1211
+ (show) => show.progressStatus === 'completed'
1212
+ ).length
1164
1213
  } else if (content.lesson_count > 0) {
1165
- const lessonIds = getLeafNodes(content);
1166
- const progressOnItems = await getProgressStateByIds(lessonIds);
1167
- completedChildren = Object.values(progressOnItems).filter(value => value === 'completed').length;
1214
+ const lessonIds = getLeafNodes(content)
1215
+ const progressOnItems = await getProgressStateByIds(lessonIds)
1216
+ completedChildren = Object.values(progressOnItems).filter(
1217
+ (value) => value === 'completed'
1218
+ ).length
1168
1219
  }
1169
1220
  return completedChildren
1170
1221
  }
1171
1222
 
1172
1223
  async function processPlaylistItem(item) {
1173
- const playlist = item.playlist;
1224
+ const playlist = item.playlist
1174
1225
  return {
1175
- id: playlist.id,
1176
- progressType: 'playlist',
1177
- header: 'playlist',
1178
- pinned: item.pinned ?? false,
1179
- playlist: playlist,
1180
- body: {
1226
+ id: playlist.id,
1227
+ progressType: 'playlist',
1228
+ header: 'playlist',
1229
+ pinned: item.pinned ?? false,
1230
+ playlist: playlist,
1231
+ body: {
1181
1232
  first_items_thumbnail_url: playlist.first_items_thumbnail_url,
1182
1233
  title: playlist.name,
1183
1234
  subtitle: `${playlist.duration_formated} • ${playlist.total_items} items • ${playlist.likes} likes • ${playlist.user.display_name}`,
@@ -1187,14 +1238,14 @@ async function processPlaylistItem(item) {
1187
1238
  cta: {
1188
1239
  text: 'Continue',
1189
1240
  action: {
1190
- brand: playlist.brand,
1241
+ brand: playlist.brand,
1191
1242
  item_id: playlist.navigateTo.id ?? null,
1192
1243
  content_id: playlist.navigateTo.content_id ?? null,
1193
- type: 'playlists',
1244
+ type: 'playlists',
1194
1245
  // TODO depreciated, maintained to avoid breaking changes
1195
- id: playlist.id,
1196
- }
1197
- }
1246
+ id: playlist.id,
1247
+ },
1248
+ },
1198
1249
  }
1199
1250
  }
1200
1251
 
@@ -1209,18 +1260,18 @@ const getFormattedType = (type, brand) => {
1209
1260
  }
1210
1261
 
1211
1262
  function getLeafNodes(content) {
1212
- const ids = [];
1263
+ const ids = []
1213
1264
  function traverse(children) {
1214
1265
  for (const item of children) {
1215
1266
  if (item.children) {
1216
- traverse(item.children); // Recursively handle nested lessons
1267
+ traverse(item.children) // Recursively handle nested lessons
1217
1268
  } else if (item.id) {
1218
- ids.push(item.id);
1269
+ ids.push(item.id)
1219
1270
  }
1220
1271
  }
1221
1272
  }
1222
1273
  if (content && Array.isArray(content.children)) {
1223
- traverse(content.children);
1274
+ traverse(content.children)
1224
1275
  }
1225
1276
  return ids
1226
1277
  }
@@ -1237,7 +1288,7 @@ async function getEligiblePlaylistItems(playlists) {
1237
1288
  progressTimestamp: timestamp / 1000,
1238
1289
  playlist: p,
1239
1290
  id: p.id,
1240
- };
1291
+ }
1241
1292
  })
1242
1293
  )
1243
1294
  }
@@ -1247,7 +1298,7 @@ function mergeAndSortItems(items, limit) {
1247
1298
  const deduped = []
1248
1299
 
1249
1300
  for (const item of items) {
1250
- const key = `${item.id}-${item.type}`;
1301
+ const key = `${item.id}-${item.type}`
1251
1302
  if (!seen.has(key)) {
1252
1303
  seen.add(key)
1253
1304
  deduped.push(item)
@@ -1255,12 +1306,12 @@ function mergeAndSortItems(items, limit) {
1255
1306
  }
1256
1307
 
1257
1308
  return deduped
1258
- .filter(item => typeof item.progressTimestamp === 'number' && item.progressTimestamp >= 0)
1309
+ .filter((item) => typeof item.progressTimestamp === 'number' && item.progressTimestamp >= 0)
1259
1310
  .sort((a, b) => {
1260
- if (a.pinned && !b.pinned) return -1;
1261
- if (!a.pinned && b.pinned) return 1;
1311
+ if (a.pinned && !b.pinned) return -1
1312
+ if (!a.pinned && b.pinned) return 1
1262
1313
  // TODO pinned guided course should always be before user pinned item
1263
- return b.progressTimestamp - a.progressTimestamp;
1314
+ return b.progressTimestamp - a.progressTimestamp
1264
1315
  })
1265
1316
  .slice(0, limit + 5)
1266
1317
  }
@@ -1288,39 +1339,36 @@ export function findIncompleteLesson(progressOnItems, currentContentId, contentT
1288
1339
 
1289
1340
  async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playlistItems) {
1290
1341
  if (!pinned) return null
1291
- const {id, progressType, pinnedAt} = pinned
1342
+ const { id, progressType, pinnedAt } = pinned
1292
1343
  let item = null
1293
1344
  if (progressType === 'content') {
1294
1345
  const pinnedId = parseInt(id)
1295
1346
  if (contentsMap.has(pinnedId)) {
1296
1347
  item = contentsMap.get(pinnedId)
1297
1348
  contentsMap.delete(pinnedId)
1298
-
1299
1349
  } else {
1300
1350
  // we use fetchByRailContentIds so that we don't have the _type restriction in the query
1301
1351
  let data = await fetchByRailContentIds([id], 'progress-tracker')
1302
- item = await addContextToContent(() => data[0] ?? null,
1303
- {
1304
- addNextLesson: true,
1305
- addNavigateTo: true,
1306
- addProgressStatus: true,
1307
- addProgressPercentage: true,
1308
- addProgressTimestamp: true
1309
- }
1310
- )
1352
+ item = await addContextToContent(() => data[0] ?? null, {
1353
+ addNextLesson: true,
1354
+ addNavigateTo: true,
1355
+ addProgressStatus: true,
1356
+ addProgressPercentage: true,
1357
+ addProgressTimestamp: true,
1358
+ })
1311
1359
  }
1312
1360
  }
1313
1361
  if (progressType === 'playlist') {
1314
- const pinnedPlaylist = playlistItems.find(p => p.playlist.id === id)
1362
+ const pinnedPlaylist = playlistItems.find((p) => p.playlist.id === id)
1315
1363
  if (pinnedPlaylist) {
1316
- playlistItems = playlistItems.filter(p => p.playlist.id !== id)
1364
+ playlistItems = playlistItems.filter((p) => p.playlist.id !== id)
1317
1365
  item = pinnedPlaylist
1318
1366
  } else {
1319
1367
  const playlist = await fetchPlaylist(id)
1320
1368
  item = {
1321
- id: id,
1322
- playlist: playlist,
1323
- type: 'playlist',
1369
+ id: id,
1370
+ playlist: playlist,
1371
+ type: 'playlist',
1324
1372
  progressTimestamp: new Date(pinnedAt).getTime(),
1325
1373
  }
1326
1374
  }
@@ -1329,11 +1377,11 @@ async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playl
1329
1377
  }
1330
1378
 
1331
1379
  function popContentAndRemoveChildrenFromContentsMap(content, contentsMap) {
1332
- const children = content.children.map(child => child.id)
1380
+ const children = content.children.map((child) => child.id)
1333
1381
  if (contentsMap.has(content.id)) {
1334
1382
  contentsMap.delete(content.id)
1335
1383
  }
1336
- children.forEach(child => {
1384
+ children.forEach((child) => {
1337
1385
  if (contentsMap.has(child)) {
1338
1386
  contentsMap.delete(child)
1339
1387
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/test/log.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes