musora-content-services 2.74.1 → 2.75.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 (73) hide show
  1. package/CHANGELOG.md +7 -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 +62 -18
  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 +2 -2
  22. package/docs/index.html +2 -2
  23. package/docs/liveTesting.ts.html +2 -2
  24. package/docs/module-Accounts.html +2 -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 +262 -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 +185 -0
  47. package/docs/railcontent.js.html +2 -2
  48. package/docs/sanity.js.html +2 -2
  49. package/docs/userActivity.js.html +120 -64
  50. package/docs/user_account.ts.html +2 -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 +2 -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 +5 -2
  66. package/src/index.d.ts +11 -0
  67. package/src/index.js +11 -0
  68. package/src/services/content-org/learning-paths.ts +33 -4
  69. package/src/services/contentAggregator.js +2 -0
  70. package/src/services/contentProgress.js +1 -0
  71. package/src/services/dateUtils.js +25 -22
  72. package/src/services/progress-row/method-card.js +113 -0
  73. package/src/services/userActivity.js +118 -62
@@ -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,15 @@ 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
1058
 
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
- ])
1059
+ const [recentPlaylists, progressContents, userPinnedItem, methodCard] = await Promise.all([
1060
+ fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
1061
+ getAllStartedOrCompleted({ onlyIds: false, brand: brand }),
1062
+ getUserPinnedItem(brand),
1063
+ getMethodCard(brand),
1064
+ ])
1046
1065
 
1047
1066
  const playlists = recentPlaylists?.data || []
1048
1067
  const eligiblePlaylistItems = await getEligiblePlaylistItems(playlists)
@@ -1050,40 +1069,59 @@ export async function getProgressRows({ brand = null, limit = 8 } = {}) {
1050
1069
  (item) => item.playlist.last_engaged_on
1051
1070
  )
1052
1071
 
1072
+ // todo post v2: refactor this once we migrate playlist progress tracking to new system
1053
1073
  const nonPlaylistContentIds = Object.keys(progressContents)
1054
1074
  if (userPinnedItem?.progressType === 'content') {
1055
1075
  nonPlaylistContentIds.push(userPinnedItem.id)
1056
1076
  }
1057
-
1077
+ //need to update addContextToContent to accept collection info
1058
1078
  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([]),
1079
+ playlistEngagedOnContents
1080
+ ? addContextToContent(fetchByRailContentIds, playlistEngagedOnContents, 'progress-tracker', {
1081
+ addNextLesson: true,
1082
+ addNavigateTo: true,
1083
+ addProgressStatus: true,
1084
+ addProgressPercentage: true,
1085
+ addProgressTimestamp: true,
1086
+ })
1087
+ : Promise.resolve([]),
1088
+ nonPlaylistContentIds
1089
+ ? addContextToContent(
1090
+ fetchByRailContentIds,
1091
+ nonPlaylistContentIds,
1092
+ 'progress-tracker',
1093
+ brand,
1094
+ {
1095
+ addNextLesson: true,
1096
+ addNavigateTo: true,
1097
+ addProgressStatus: true,
1098
+ addProgressPercentage: true,
1099
+ addProgressTimestamp: true,
1100
+ }
1101
+ )
1102
+ : Promise.resolve([]),
1073
1103
  ])
1104
+
1074
1105
  const contentsMap = generateContentsMap(contents, playlistsContents)
1106
+
1075
1107
  let combined = await extractPinnedItemsAndSortAllItems(
1076
1108
  userPinnedItem,
1077
1109
  contentsMap,
1078
1110
  eligiblePlaylistItems,
1111
+ methodCard,
1079
1112
  limit
1080
1113
  )
1081
1114
  const results = await Promise.all(
1082
- combined
1083
- .slice(0, limit)
1084
- .map((item) =>
1085
- item.type === 'playlist' ? processPlaylistItem(item) : processContentItem(item)
1086
- )
1115
+ combined.slice(0, limit).map((item) => {
1116
+ switch (item.type) {
1117
+ case 'playlist':
1118
+ return processPlaylistItem(item)
1119
+ case 'method':
1120
+ return item
1121
+ default:
1122
+ return processContentItem(item)
1123
+ }
1124
+ })
1087
1125
  )
1088
1126
  return {
1089
1127
  type: TabResponseType.PROGRESS_ROWS,
@@ -1118,7 +1156,11 @@ async function processContentItem(content) {
1118
1156
  })
1119
1157
  content.time_remaining_seconds = timeRemaining.totalSeconds
1120
1158
  ctaText = 'Next lesson in ' + timeRemaining.formatted
1121
- } else if (!content.progressStatus || content.progressStatus === 'not-started' || content.progressPercentage === 0) {
1159
+ } else if (
1160
+ !content.progressStatus ||
1161
+ content.progressStatus === 'not-started' ||
1162
+ content.progressPercentage === 0
1163
+ ) {
1122
1164
  ctaText = 'Start Course'
1123
1165
  }
1124
1166
  }
@@ -1136,6 +1178,7 @@ async function processContentItem(content) {
1136
1178
  content = shows.find((lesson) => lesson.id === nextByProgress)
1137
1179
  content.completed_children = completedShows
1138
1180
  content.progressTimestamp = progressTimestamp
1181
+ content.progressTimestamp = progressTimestamp
1139
1182
  content.pinned = wasPinned
1140
1183
  }
1141
1184
  content.child_count = shows.length
@@ -1144,23 +1187,26 @@ async function processContentItem(content) {
1144
1187
  ctaText = 'Revisit Show'
1145
1188
  }
1146
1189
  }
1147
-
1190
+ console.log('Progress Timestamp', content.progressTimestamp)
1148
1191
  return {
1149
- id: content.id,
1150
- progressType: 'content',
1151
- header: contentType,
1152
- pinned: content.pinned ?? false,
1153
- content: content,
1154
- body: {
1192
+ id: content.id,
1193
+ progressType: 'content',
1194
+ header: contentType,
1195
+ pinned: content.pinned ?? false,
1196
+ content: content,
1197
+ body: {
1155
1198
  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}`
1199
+ thumbnail: content.thumbnail,
1200
+ title: content.title,
1201
+ isLive: isLive,
1202
+ badge: content.badge ?? null,
1203
+ isLocked: content.is_locked ?? false,
1204
+ subtitle:
1205
+ collectionLessonTypes.includes(content.type) || content.lesson_count > 1
1206
+ ? `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`
1207
+ : contentType === 'lesson' && isLive === false
1208
+ ? `${content.progressPercentage}% Complete`
1209
+ : `${content.difficulty_string} • ${content.artist_name}`,
1164
1210
  },
1165
1211
  cta: {
1166
1212
  text: ctaText,
@@ -1216,6 +1262,7 @@ async function getCompletedChildren(content, contentType) {
1216
1262
 
1217
1263
  async function processPlaylistItem(item) {
1218
1264
  const playlist = item.playlist
1265
+
1219
1266
  return {
1220
1267
  id: playlist.id,
1221
1268
  progressType: 'playlist',
@@ -1304,7 +1351,6 @@ function mergeAndSortItems(items, limit) {
1304
1351
  .sort((a, b) => {
1305
1352
  if (a.pinned && !b.pinned) return -1
1306
1353
  if (!a.pinned && b.pinned) return 1
1307
- // TODO pinned guided course should always be before user pinned item
1308
1354
  return b.progressTimestamp - a.progressTimestamp
1309
1355
  })
1310
1356
  .slice(0, limit + 5)
@@ -1312,7 +1358,7 @@ function mergeAndSortItems(items, limit) {
1312
1358
 
1313
1359
  export function findIncompleteLesson(progressOnItems, currentContentId, contentType) {
1314
1360
  const ids = Object.keys(progressOnItems).map(Number)
1315
- if (contentType === 'guided-course') {
1361
+ if (contentType === 'guided-course' || contentType === 'learning-path') {
1316
1362
  // Return first incomplete lesson
1317
1363
  return ids.find((id) => progressOnItems[id] !== 'completed') || ids.at(0)
1318
1364
  }
@@ -1331,11 +1377,16 @@ export function findIncompleteLesson(progressOnItems, currentContentId, contentT
1331
1377
  return ids[0]
1332
1378
  }
1333
1379
 
1334
- async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playlistItems) {
1380
+ async function popPinnedItemFromContentsOrPlaylistMap(
1381
+ pinned,
1382
+ contentsMap,
1383
+ playlistItems,
1384
+ methodCard
1385
+ ) {
1335
1386
  if (!pinned) return null
1336
1387
  const { id, pinnedAt } = pinned
1337
1388
  let item = null
1338
- const progressType = pinned.progressType ?? pinned.type;
1389
+ const progressType = pinned.progressType ?? pinned.type
1339
1390
 
1340
1391
  if (progressType === 'content') {
1341
1392
  const pinnedId = parseInt(id)
@@ -1369,12 +1420,17 @@ async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playl
1369
1420
  }
1370
1421
  }
1371
1422
  }
1423
+ if (progressType === 'method') {
1424
+ // simply get method card and return
1425
+ item = methodCard
1426
+ //todo remove method card
1427
+ }
1372
1428
  return item
1373
1429
  }
1374
1430
 
1375
1431
  function popContentAndRemoveChildrenFromContentsMap(content, contentsMap) {
1376
- if (!content.children || content.children.length === 0){
1377
- console.warn(`content ${content.id} has no children`, content);
1432
+ if (!content.children || content.children.length === 0) {
1433
+ console.warn(`content ${content.id} has no children`, content)
1378
1434
  } else {
1379
1435
  const children = content.children.map((child) => child.id)
1380
1436
  if (contentsMap.has(content.id)) {