musora-content-services 2.74.0 → 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.
- package/CHANGELOG.md +14 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Forums.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/TestUser.html +2 -2
- package/docs/UserManagementSystem.html +2 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +2 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_guided-courses.ts.html +2 -2
- package/docs/content-org_learning-paths.ts.html +62 -18
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +2 -2
- package/docs/forums_categories.ts.html +2 -2
- package/docs/forums_forums.ts.html +2 -2
- package/docs/forums_posts.ts.html +2 -2
- package/docs/forums_threads.ts.html +2 -2
- package/docs/gamification_awards.ts.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/global.html +2 -2
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +2 -2
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +2 -2
- package/docs/module-Forums.html +2 -2
- package/docs/module-GuidedCourses.html +2 -2
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +262 -9
- package/docs/module-Onboarding.html +2 -2
- package/docs/module-Payments.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +2 -2
- package/docs/module-ProgressRow.html +108 -0
- package/docs/module-Railcontent-Services.html +2 -2
- package/docs/module-Sanity-Services.html +2 -2
- package/docs/module-Sessions.html +2 -2
- package/docs/module-UserActivity.html +22 -22
- package/docs/module-UserChat.html +2 -2
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserMemberships.html +2 -2
- package/docs/module-UserNotifications.html +2 -2
- package/docs/module-UserProfile.html +2 -2
- package/docs/progress-row_method-card.js.html +185 -0
- package/docs/railcontent.js.html +2 -2
- package/docs/sanity.js.html +2 -2
- package/docs/userActivity.js.html +120 -64
- package/docs/user_account.ts.html +2 -2
- package/docs/user_chat.js.html +2 -2
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_memberships.ts.html +2 -2
- package/docs/user_notifications.js.html +2 -2
- package/docs/user_onboarding.ts.html +2 -2
- package/docs/user_payments.ts.html +2 -2
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_profile.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +2 -2
- package/docs/user_user-management-system.js.html +2 -2
- package/jsdoc.json +1 -0
- package/package.json +1 -1
- package/src/contentTypeConfig.js +5 -2
- package/src/index.d.ts +11 -0
- package/src/index.js +11 -0
- package/src/services/content-org/learning-paths.ts +35 -5
- package/src/services/contentAggregator.js +2 -0
- package/src/services/contentProgress.js +1 -0
- package/src/services/dateUtils.js +25 -22
- package/src/services/progress-row/method-card.js +113 -0
- 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 {
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
-
.
|
|
1084
|
-
|
|
1085
|
-
|
|
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 (
|
|
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:
|
|
1150
|
-
progressType:
|
|
1151
|
-
header:
|
|
1152
|
-
pinned:
|
|
1153
|
-
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:
|
|
1157
|
-
title:
|
|
1158
|
-
isLive:
|
|
1159
|
-
badge:
|
|
1160
|
-
isLocked:
|
|
1161
|
-
subtitle:
|
|
1162
|
-
|
|
1163
|
-
|
|
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(
|
|
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)) {
|