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.
- package/CHANGELOG.md +22 -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 +95 -31
- 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 +56 -8
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +191 -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 +602 -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 +182 -0
- package/docs/railcontent.js.html +2 -2
- package/docs/sanity.js.html +3 -3
- package/docs/userActivity.js.html +120 -65
- package/docs/user_account.ts.html +29 -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 +4 -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 +18 -8
- package/src/index.d.ts +20 -1
- package/src/index.js +20 -1
- package/src/infrastructure/http/HttpClient.ts +5 -5
- package/src/services/content-org/learning-paths.ts +77 -28
- package/src/services/contentAggregator.js +56 -45
- package/src/services/contentProgress.js +7 -5
- package/src/services/dateUtils.js +25 -22
- package/src/services/progress-row/method-card.js +110 -0
- package/src/services/sanity.js +1 -1
- package/src/services/user/account.ts +27 -0
- package/src/services/user/types.js +2 -0
- package/src/services/userActivity.js +118 -63
- 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 {
|
|
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,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 =
|
|
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
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
-
.
|
|
1084
|
-
|
|
1085
|
-
|
|
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 (
|
|
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:
|
|
1150
|
-
progressType:
|
|
1151
|
-
header:
|
|
1152
|
-
pinned:
|
|
1153
|
-
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:
|
|
1157
|
-
title:
|
|
1158
|
-
isLive:
|
|
1159
|
-
badge:
|
|
1160
|
-
isLocked:
|
|
1161
|
-
subtitle:
|
|
1162
|
-
|
|
1163
|
-
|
|
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(
|
|
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 {
|
|
3
|
-
|
|
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
|
|
13
|
+
const results = await getLearningPath(417140)
|
|
12
14
|
})
|
|
13
15
|
test('getlearningPathLessonsTestNew', async () => {
|
|
14
16
|
await contentStatusCompleted(417105)
|