musora-content-services 2.74.1 → 2.76.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/docs/ContentOrganization.html +2 -2
  3. package/docs/Forums.html +2 -2
  4. package/docs/Gamification.html +2 -2
  5. package/docs/TestUser.html +2 -2
  6. package/docs/UserManagementSystem.html +2 -2
  7. package/docs/api_types.js.html +2 -2
  8. package/docs/config.js.html +2 -2
  9. package/docs/content-org_content-org.js.html +2 -2
  10. package/docs/content-org_guided-courses.ts.html +2 -2
  11. package/docs/content-org_learning-paths.ts.html +95 -31
  12. package/docs/content-org_playlists-types.js.html +2 -2
  13. package/docs/content-org_playlists.js.html +2 -2
  14. package/docs/content.js.html +2 -2
  15. package/docs/forums_categories.ts.html +2 -2
  16. package/docs/forums_forums.ts.html +2 -2
  17. package/docs/forums_posts.ts.html +2 -2
  18. package/docs/forums_threads.ts.html +2 -2
  19. package/docs/gamification_awards.ts.html +2 -2
  20. package/docs/gamification_gamification.js.html +2 -2
  21. package/docs/global.html +56 -8
  22. package/docs/index.html +2 -2
  23. package/docs/liveTesting.ts.html +2 -2
  24. package/docs/module-Accounts.html +191 -2
  25. package/docs/module-Awards.html +2 -2
  26. package/docs/module-Config.html +2 -2
  27. package/docs/module-Content-Services-V2.html +2 -2
  28. package/docs/module-Forums.html +2 -2
  29. package/docs/module-GuidedCourses.html +2 -2
  30. package/docs/module-Interests.html +2 -2
  31. package/docs/module-LearningPaths.html +602 -9
  32. package/docs/module-Onboarding.html +2 -2
  33. package/docs/module-Payments.html +2 -2
  34. package/docs/module-Permissions.html +2 -2
  35. package/docs/module-Playlists.html +2 -2
  36. package/docs/module-ProgressRow.html +108 -0
  37. package/docs/module-Railcontent-Services.html +2 -2
  38. package/docs/module-Sanity-Services.html +2 -2
  39. package/docs/module-Sessions.html +2 -2
  40. package/docs/module-UserActivity.html +22 -22
  41. package/docs/module-UserChat.html +2 -2
  42. package/docs/module-UserManagement.html +2 -2
  43. package/docs/module-UserMemberships.html +2 -2
  44. package/docs/module-UserNotifications.html +2 -2
  45. package/docs/module-UserProfile.html +2 -2
  46. package/docs/progress-row_method-card.js.html +182 -0
  47. package/docs/railcontent.js.html +2 -2
  48. package/docs/sanity.js.html +3 -3
  49. package/docs/userActivity.js.html +120 -65
  50. package/docs/user_account.ts.html +29 -2
  51. package/docs/user_chat.js.html +2 -2
  52. package/docs/user_interests.js.html +2 -2
  53. package/docs/user_management.js.html +2 -2
  54. package/docs/user_memberships.ts.html +2 -2
  55. package/docs/user_notifications.js.html +2 -2
  56. package/docs/user_onboarding.ts.html +2 -2
  57. package/docs/user_payments.ts.html +2 -2
  58. package/docs/user_permissions.js.html +2 -2
  59. package/docs/user_profile.js.html +2 -2
  60. package/docs/user_sessions.js.html +2 -2
  61. package/docs/user_types.js.html +4 -2
  62. package/docs/user_user-management-system.js.html +2 -2
  63. package/jsdoc.json +1 -0
  64. package/package.json +1 -1
  65. package/src/contentTypeConfig.js +18 -8
  66. package/src/index.d.ts +20 -1
  67. package/src/index.js +20 -1
  68. package/src/infrastructure/http/HttpClient.ts +5 -5
  69. package/src/services/content-org/learning-paths.ts +77 -28
  70. package/src/services/contentAggregator.js +56 -45
  71. package/src/services/contentProgress.js +7 -5
  72. package/src/services/dateUtils.js +25 -22
  73. package/src/services/progress-row/method-card.js +110 -0
  74. package/src/services/sanity.js +1 -1
  75. package/src/services/user/account.ts +27 -0
  76. package/src/services/user/types.js +2 -0
  77. package/src/services/userActivity.js +118 -63
  78. package/test/learningPaths.test.js +5 -3
@@ -11,9 +11,14 @@ import {
11
11
  fetchRecentUserActivities,
12
12
  } from './railcontent'
13
13
  import { DataContext, UserActivityVersionKey } from './dataContext.js'
14
- import { fetchByRailContentId, fetchByRailContentIds, fetchShows } from './sanity'
14
+ import {
15
+ fetchByRailContentId,
16
+ fetchByRailContentIds,
17
+ fetchMethodV2IntroVideo,
18
+ fetchShows,
19
+ } from './sanity'
15
20
  import { fetchPlaylist, fetchUserPlaylists } from './content-org/playlists'
16
- import {guidedCourses} from './content-org/guided-courses'
21
+ import { guidedCourses } from './content-org/guided-courses'
17
22
  import {
18
23
  getMonday,
19
24
  getWeekNumber,
@@ -36,6 +41,7 @@ import dayjs from 'dayjs'
36
41
  import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
37
42
  import weekOfYear from 'dayjs/plugin/weekOfYear'
38
43
  import { addContextToContent } from './contentAggregator.js'
44
+ import { getMethodCard } from './progress-row/method-card.js'
39
45
 
40
46
  const DATA_KEY_PRACTICES = 'practices'
41
47
  const DATA_KEY_LAST_UPDATED_TIME = 'u'
@@ -861,7 +867,8 @@ async function formatPracticeMeta(practices = []) {
861
867
  })
862
868
 
863
869
  return practices.map((practice) => {
864
- const content = contents && contents.length > 0 ? contents.find((c) => c.id === practice.content_id) : {}
870
+ const content =
871
+ contents && contents.length > 0 ? contents.find((c) => c.id === practice.content_id) : {}
865
872
 
866
873
  return {
867
874
  id: practice.id,
@@ -950,12 +957,15 @@ async function extractPinnedItemsAndSortAllItems(
950
957
  userPinnedItem,
951
958
  contentsMap,
952
959
  eligiblePlaylistItems,
960
+ methodCard,
961
+ //method contents
953
962
  limit
954
963
  ) {
955
964
  let pinnedItem = await popPinnedItemFromContentsOrPlaylistMap(
956
965
  userPinnedItem,
957
966
  contentsMap,
958
- eligiblePlaylistItems
967
+ eligiblePlaylistItems,
968
+ methodCard
959
969
  )
960
970
 
961
971
  let combined = []
@@ -965,18 +975,19 @@ async function extractPinnedItemsAndSortAllItems(
965
975
  combined.push(pinnedItem)
966
976
  }
967
977
 
978
+ if (!(pinnedItem && pinnedItem.progressType === 'method')) {
979
+ combined.push(methodCard)
980
+ }
981
+
968
982
  const progressList = Array.from(contentsMap.values())
983
+ //need another for methodContents?
984
+
969
985
  combined = [...combined, ...progressList, ...eligiblePlaylistItems]
970
986
  return mergeAndSortItems(combined, limit)
971
987
  }
972
988
 
973
- function generateContentsMap(contents, playlistsContents) {
974
- const excludedTypes = new Set([
975
- 'pack-bundle',
976
- 'learning-path-course',
977
- 'learning-path-level',
978
- 'guided-course-part',
979
- ])
989
+ function generateContentsMap(contents, playlistsContents, methodProgressContents) {
990
+ const excludedTypes = new Set(['pack-bundle', 'guided-course-part'])
980
991
  const existingShows = new Set()
981
992
  const contentsMap = new Map()
982
993
  const childToParentMap = {}
@@ -1008,8 +1019,6 @@ function generateContentsMap(contents, playlistsContents) {
1008
1019
  }
1009
1020
  })
1010
1021
 
1011
- // TODO this doesn't work for guided courses as the GC card takes precedence over the playlist card
1012
- // https://musora.atlassian.net/browse/BEH-812
1013
1022
  if (playlistsContents) {
1014
1023
  for (const item of playlistsContents) {
1015
1024
  const contentId = item.id
@@ -1018,6 +1027,16 @@ function generateContentsMap(contents, playlistsContents) {
1018
1027
  parentIds.forEach((id) => contentsMap.delete(id))
1019
1028
  }
1020
1029
  }
1030
+ //TODO:: remove method cards from progress rows
1031
+ // if (methodProgressContents && Object.keys(methodProgressContents).length) {
1032
+ // for (const item of methodProgressContents) {
1033
+ // const contentId = item.id
1034
+ // contentsMap.delete(contentId)
1035
+ // const parentIds = item.parent_content_data || []
1036
+ // parentIds.forEach((id) => contentsMap.delete(id))
1037
+ // }
1038
+ // }
1039
+
1021
1040
  return contentsMap
1022
1041
  }
1023
1042
 
@@ -1034,15 +1053,14 @@ function generateContentsMap(contents, playlistsContents) {
1034
1053
  * .then(data => console.log(data))
1035
1054
  * .catch(error => console.error(error));
1036
1055
  */
1037
- export async function getProgressRows({ brand = null, limit = 8 } = {}) {
1056
+ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
1038
1057
  // TODO slice progress to a reasonable number, say 100
1039
-
1040
- const [recentPlaylists, progressContents, userPinnedItem] =
1041
- await Promise.all([
1042
- fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
1043
- getAllStartedOrCompleted({ onlyIds: false, brand: brand }),
1044
- getUserPinnedItem(brand),
1045
- ])
1058
+ const methodCardPromise = getMethodCard(brand)
1059
+ const [recentPlaylists, progressContents, userPinnedItem] = await Promise.all([
1060
+ fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
1061
+ getAllStartedOrCompleted({ onlyIds: false, brand: brand }),
1062
+ getUserPinnedItem(brand),
1063
+ ])
1046
1064
 
1047
1065
  const playlists = recentPlaylists?.data || []
1048
1066
  const eligiblePlaylistItems = await getEligiblePlaylistItems(playlists)
@@ -1050,40 +1068,59 @@ export async function getProgressRows({ brand = null, limit = 8 } = {}) {
1050
1068
  (item) => item.playlist.last_engaged_on
1051
1069
  )
1052
1070
 
1071
+ // todo post v2: refactor this once we migrate playlist progress tracking to new system
1053
1072
  const nonPlaylistContentIds = Object.keys(progressContents)
1054
1073
  if (userPinnedItem?.progressType === 'content') {
1055
1074
  nonPlaylistContentIds.push(userPinnedItem.id)
1056
1075
  }
1057
-
1076
+ //need to update addContextToContent to accept collection info
1058
1077
  const [playlistsContents, contents] = await Promise.all([
1059
- playlistEngagedOnContents ? addContextToContent(fetchByRailContentIds, playlistEngagedOnContents, 'progress-tracker', {
1060
- addNextLesson: true,
1061
- addNavigateTo: true,
1062
- addProgressStatus: true,
1063
- addProgressPercentage: true,
1064
- addProgressTimestamp: true,
1065
- }) : Promise.resolve([]),
1066
- nonPlaylistContentIds ? addContextToContent(fetchByRailContentIds, nonPlaylistContentIds, 'progress-tracker', brand, {
1067
- addNextLesson: true,
1068
- addNavigateTo: true,
1069
- addProgressStatus: true,
1070
- addProgressPercentage: true,
1071
- addProgressTimestamp: true,
1072
- }) : Promise.resolve([]),
1078
+ playlistEngagedOnContents
1079
+ ? addContextToContent(fetchByRailContentIds, playlistEngagedOnContents, 'progress-tracker', {
1080
+ addNextLesson: true,
1081
+ addNavigateTo: true,
1082
+ addProgressStatus: true,
1083
+ addProgressPercentage: true,
1084
+ addProgressTimestamp: true,
1085
+ })
1086
+ : Promise.resolve([]),
1087
+ nonPlaylistContentIds
1088
+ ? addContextToContent(
1089
+ fetchByRailContentIds,
1090
+ nonPlaylistContentIds,
1091
+ 'progress-tracker',
1092
+ brand,
1093
+ {
1094
+ addNextLesson: true,
1095
+ addNavigateTo: true,
1096
+ addProgressStatus: true,
1097
+ addProgressPercentage: true,
1098
+ addProgressTimestamp: true,
1099
+ }
1100
+ )
1101
+ : Promise.resolve([]),
1073
1102
  ])
1103
+
1074
1104
  const contentsMap = generateContentsMap(contents, playlistsContents)
1105
+ const methodCard = await methodCardPromise
1075
1106
  let combined = await extractPinnedItemsAndSortAllItems(
1076
1107
  userPinnedItem,
1077
1108
  contentsMap,
1078
1109
  eligiblePlaylistItems,
1110
+ methodCard,
1079
1111
  limit
1080
1112
  )
1081
1113
  const results = await Promise.all(
1082
- combined
1083
- .slice(0, limit)
1084
- .map((item) =>
1085
- item.type === 'playlist' ? processPlaylistItem(item) : processContentItem(item)
1086
- )
1114
+ combined.slice(0, limit).map((item) => {
1115
+ switch (item.type) {
1116
+ case 'playlist':
1117
+ return processPlaylistItem(item)
1118
+ case 'method':
1119
+ return item
1120
+ default:
1121
+ return processContentItem(item)
1122
+ }
1123
+ })
1087
1124
  )
1088
1125
  return {
1089
1126
  type: TabResponseType.PROGRESS_ROWS,
@@ -1118,7 +1155,11 @@ async function processContentItem(content) {
1118
1155
  })
1119
1156
  content.time_remaining_seconds = timeRemaining.totalSeconds
1120
1157
  ctaText = 'Next lesson in ' + timeRemaining.formatted
1121
- } else if (!content.progressStatus || content.progressStatus === 'not-started' || content.progressPercentage === 0) {
1158
+ } else if (
1159
+ !content.progressStatus ||
1160
+ content.progressStatus === 'not-started' ||
1161
+ content.progressPercentage === 0
1162
+ ) {
1122
1163
  ctaText = 'Start Course'
1123
1164
  }
1124
1165
  }
@@ -1136,6 +1177,7 @@ async function processContentItem(content) {
1136
1177
  content = shows.find((lesson) => lesson.id === nextByProgress)
1137
1178
  content.completed_children = completedShows
1138
1179
  content.progressTimestamp = progressTimestamp
1180
+ content.progressTimestamp = progressTimestamp
1139
1181
  content.pinned = wasPinned
1140
1182
  }
1141
1183
  content.child_count = shows.length
@@ -1144,23 +1186,26 @@ async function processContentItem(content) {
1144
1186
  ctaText = 'Revisit Show'
1145
1187
  }
1146
1188
  }
1147
-
1189
+ console.log('Progress Timestamp', content.progressTimestamp)
1148
1190
  return {
1149
- id: content.id,
1150
- progressType: 'content',
1151
- header: contentType,
1152
- pinned: content.pinned ?? false,
1153
- content: content,
1154
- body: {
1191
+ id: content.id,
1192
+ progressType: 'content',
1193
+ header: contentType,
1194
+ pinned: content.pinned ?? false,
1195
+ content: content,
1196
+ body: {
1155
1197
  progressPercent: isLive ? undefined : content.progressPercentage,
1156
- thumbnail: content.thumbnail,
1157
- title: content.title,
1158
- isLive: isLive,
1159
- badge: content.badge ?? null,
1160
- isLocked: content.is_locked ?? false,
1161
- subtitle: collectionLessonTypes.includes(content.type) || content.lesson_count > 1
1162
- ? `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`
1163
- : (contentType === 'lesson' && isLive === false) ? `${content.progressPercentage}% Complete`: `${content.difficulty_string} ${content.artist_name}`
1198
+ thumbnail: content.thumbnail,
1199
+ title: content.title,
1200
+ isLive: isLive,
1201
+ badge: content.badge ?? null,
1202
+ isLocked: content.is_locked ?? false,
1203
+ subtitle:
1204
+ collectionLessonTypes.includes(content.type) || content.lesson_count > 1
1205
+ ? `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`
1206
+ : contentType === 'lesson' && isLive === false
1207
+ ? `${content.progressPercentage}% Complete`
1208
+ : `${content.difficulty_string} • ${content.artist_name}`,
1164
1209
  },
1165
1210
  cta: {
1166
1211
  text: ctaText,
@@ -1216,6 +1261,7 @@ async function getCompletedChildren(content, contentType) {
1216
1261
 
1217
1262
  async function processPlaylistItem(item) {
1218
1263
  const playlist = item.playlist
1264
+
1219
1265
  return {
1220
1266
  id: playlist.id,
1221
1267
  progressType: 'playlist',
@@ -1304,7 +1350,6 @@ function mergeAndSortItems(items, limit) {
1304
1350
  .sort((a, b) => {
1305
1351
  if (a.pinned && !b.pinned) return -1
1306
1352
  if (!a.pinned && b.pinned) return 1
1307
- // TODO pinned guided course should always be before user pinned item
1308
1353
  return b.progressTimestamp - a.progressTimestamp
1309
1354
  })
1310
1355
  .slice(0, limit + 5)
@@ -1312,7 +1357,7 @@ function mergeAndSortItems(items, limit) {
1312
1357
 
1313
1358
  export function findIncompleteLesson(progressOnItems, currentContentId, contentType) {
1314
1359
  const ids = Object.keys(progressOnItems).map(Number)
1315
- if (contentType === 'guided-course') {
1360
+ if (contentType === 'guided-course' || contentType === 'learning-path') {
1316
1361
  // Return first incomplete lesson
1317
1362
  return ids.find((id) => progressOnItems[id] !== 'completed') || ids.at(0)
1318
1363
  }
@@ -1331,11 +1376,16 @@ export function findIncompleteLesson(progressOnItems, currentContentId, contentT
1331
1376
  return ids[0]
1332
1377
  }
1333
1378
 
1334
- async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playlistItems) {
1379
+ async function popPinnedItemFromContentsOrPlaylistMap(
1380
+ pinned,
1381
+ contentsMap,
1382
+ playlistItems,
1383
+ methodCard
1384
+ ) {
1335
1385
  if (!pinned) return null
1336
1386
  const { id, pinnedAt } = pinned
1337
1387
  let item = null
1338
- const progressType = pinned.progressType ?? pinned.type;
1388
+ const progressType = pinned.progressType ?? pinned.type
1339
1389
 
1340
1390
  if (progressType === 'content') {
1341
1391
  const pinnedId = parseInt(id)
@@ -1369,12 +1419,17 @@ async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playl
1369
1419
  }
1370
1420
  }
1371
1421
  }
1422
+ if (progressType === 'method') {
1423
+ // simply get method card and return
1424
+ item = methodCard
1425
+ //todo remove method card
1426
+ }
1372
1427
  return item
1373
1428
  }
1374
1429
 
1375
1430
  function popContentAndRemoveChildrenFromContentsMap(content, contentsMap) {
1376
- if (!content.children || content.children.length === 0){
1377
- console.warn(`content ${content.id} has no children`, content);
1431
+ if (!content.children || content.children.length === 0) {
1432
+ console.warn(`content ${content.id} has no children`, content)
1378
1433
  } else {
1379
1434
  const children = content.children.map((child) => child.id)
1380
1435
  if (contentsMap.has(content.id)) {
@@ -1,6 +1,8 @@
1
1
  import { initializeTestService } from './initializeTests.js'
2
- import { fetchLearningPathLessons } from '../src/services/content-org/learning-paths.ts'
3
- import { fetchByRailContentId } from '../src/services/sanity.js'
2
+ import {
3
+ fetchLearningPathLessons,
4
+ getLearningPath,
5
+ } from '../src/services/content-org/learning-paths.ts'
4
6
  import { contentStatusCompleted } from '../src/services/contentProgress.js'
5
7
  describe('learning-paths', function () {
6
8
  beforeEach(async () => {
@@ -8,7 +10,7 @@ describe('learning-paths', function () {
8
10
  })
9
11
 
10
12
  test('getLearningPathsV2Test', async () => {
11
- const results = await fetchByRailContentId(417140, 'learning-path-v2')
13
+ const results = await getLearningPath(417140)
12
14
  })
13
15
  test('getlearningPathLessonsTestNew', async () => {
14
16
  await contentStatusCompleted(417105)