musora-content-services 2.84.0 → 2.85.1
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/.claude/settings.local.json +14 -0
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +15 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/docs/Content.html +0 -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 -5
- 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 +24 -126
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +10 -88
- package/docs/content_artist.ts.html +0 -0
- package/docs/content_content.ts.html +0 -0
- package/docs/content_genre.ts.html +0 -0
- package/docs/content_instructor.ts.html +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/forums_categories.ts.html +3 -22
- package/docs/forums_discussions.js.html +0 -0
- package/docs/forums_forum.js.html +0 -0
- 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.js.html +0 -0
- package/docs/gamification_awards.ts.html +12 -26
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/gamification_types.js.html +0 -0
- package/docs/global.html +2 -2
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +14 -14
- package/docs/module-Artist.html +0 -0
- package/docs/module-Awards.html +6 -106
- package/docs/module-Categories.html +0 -0
- package/docs/module-Config.html +4 -5
- package/docs/module-Content-Services-V2.html +9 -440
- package/docs/module-ForumCategories.html +0 -0
- package/docs/module-ForumDiscussions.html +0 -0
- package/docs/module-Forums.html +43 -607
- package/docs/module-Genre.html +0 -0
- package/docs/module-GuidedCourses.html +2 -2
- package/docs/module-Instructor.html +0 -0
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +12 -640
- 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 +2 -2
- package/docs/module-Railcontent-Services.html +298 -31
- package/docs/module-Sanity-Services.html +1901 -530
- package/docs/module-Sessions.html +2 -2
- package/docs/module-Threads.html +0 -0
- package/docs/module-UserActivity.html +5 -5
- 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 +3 -3
- package/docs/railcontent.js.html +20 -8
- package/docs/sanity.js.html +313 -199
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/docs/userActivity.js.html +5 -17
- package/docs/user_account.ts.html +13 -14
- 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.js.html +0 -0
- 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 +3 -3
- 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/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/contentMetaData.js +0 -0
- package/src/filterBuilder.js +0 -0
- package/src/index.d.ts +2 -2
- package/src/index.js +2 -2
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/httpHelper.js +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content/artist.ts +0 -0
- package/src/services/content/content.ts +0 -0
- package/src/services/content/genre.ts +0 -0
- package/src/services/content/instructor.ts +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/learning-paths.ts +0 -0
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +0 -0
- package/src/services/contentAggregator.js +10 -23
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +250 -147
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +0 -0
- package/src/services/forums/posts.ts +0 -0
- package/src/services/forums/threads.ts +7 -7
- package/src/services/forums/types.ts +1 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/liveTesting.ts +0 -0
- package/src/services/permissions/PermissionsAdapter.ts +0 -0
- package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
- package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
- package/src/services/permissions/README.md +0 -0
- package/src/services/permissions/index.ts +0 -0
- package/src/services/progress-row/method-card.js +0 -0
- package/src/services/railcontent.js +18 -6
- package/src/services/recommendations.js +0 -0
- package/src/services/sanity.js +18 -2
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/memberships.ts +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/onboarding.ts +0 -0
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/sessions.js +0 -0
- package/src/services/user/types.d.ts +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/src/services/userActivity.js +3 -14
- package/test/HttpClient.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/learningPaths.test.js +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
postRecordWatchSession,
|
|
7
7
|
} from './railcontent.js'
|
|
8
8
|
import { DataContext, ContentProgressVersionKey } from './dataContext.js'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
fetchHierarchy,
|
|
11
|
+
fetchMethodV2StructureFromId
|
|
12
|
+
} from './sanity.js'
|
|
10
13
|
import { recordUserPractice, findIncompleteLesson } from './userActivity'
|
|
11
14
|
import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
|
|
12
15
|
|
|
@@ -17,90 +20,41 @@ const DATA_KEY_PROGRESS = 'p'
|
|
|
17
20
|
const DATA_KEY_RESUME_TIME = 't'
|
|
18
21
|
const DATA_KEY_LAST_UPDATED_TIME = 'u'
|
|
19
22
|
const DATA_KEY_BRAND = 'b'
|
|
23
|
+
const DATA_KEY_COLLECTION = 'c'
|
|
24
|
+
const DATA_CONTENT_ID = 'i'
|
|
20
25
|
|
|
21
26
|
export let dataContext = new DataContext(ContentProgressVersionKey, fetchContentProgress)
|
|
22
27
|
|
|
23
28
|
let sessionData = []
|
|
24
29
|
|
|
25
|
-
export async function getProgressPercentage(contentId) {
|
|
26
|
-
return getById(contentId, DATA_KEY_PROGRESS, 0)
|
|
30
|
+
export async function getProgressPercentage(contentId, collection = null) {
|
|
31
|
+
return getById(contentId, collection, DATA_KEY_PROGRESS, 0)
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
export async function getProgressPercentageByIds(contentIds) {
|
|
30
|
-
return getByIds(contentIds, DATA_KEY_PROGRESS, 0)
|
|
34
|
+
export async function getProgressPercentageByIds(contentIds, collection = null) {
|
|
35
|
+
return getByIds(contentIds, collection, DATA_KEY_PROGRESS, 0)
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
export async function getProgressState(contentId) {
|
|
34
|
-
return getById(contentId, DATA_KEY_STATUS, '')
|
|
38
|
+
export async function getProgressState(contentId, collection = null) {
|
|
39
|
+
return getById(contentId, collection, DATA_KEY_STATUS, '')
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
export async function getProgressStateByIds(contentIds) {
|
|
38
|
-
return getByIds(contentIds, DATA_KEY_STATUS, '')
|
|
42
|
+
export async function getProgressStateByIds(contentIds, collection = null) {
|
|
43
|
+
return getByIds(contentIds, collection, DATA_KEY_STATUS, '')
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
export async function getResumeTimeSeconds(contentId) {
|
|
42
|
-
return getById(contentId, DATA_KEY_RESUME_TIME, 0)
|
|
46
|
+
export async function getResumeTimeSeconds(contentId, collection = null) {
|
|
47
|
+
return getById(contentId, collection, DATA_KEY_RESUME_TIME, 0)
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
export async function getResumeTimeSecondsByIds(contentIds) {
|
|
46
|
-
return getByIds(contentIds, DATA_KEY_RESUME_TIME, 0)
|
|
50
|
+
export async function getResumeTimeSecondsByIds(contentIds, collection = null) {
|
|
51
|
+
return getByIds(contentIds, collection, DATA_KEY_RESUME_TIME, 0)
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
export async function
|
|
50
|
-
let nextLessonData = {}
|
|
51
|
-
|
|
52
|
-
for (const content of data) {
|
|
53
|
-
// Skip null/undefined entries (can happen when GROQ dereference doesn't match filter)
|
|
54
|
-
if (!content) continue
|
|
55
|
-
|
|
56
|
-
const children = content.children?.filter(Boolean).map((child) => child.id) ?? []
|
|
57
|
-
//only calculate nextLesson if needed, based on content type
|
|
58
|
-
if (!getNextLessonLessonParentTypes.includes(content.type)) {
|
|
59
|
-
nextLessonData[content.id] = null
|
|
60
|
-
} else {
|
|
61
|
-
//return first child if parent-content is complete or no progress
|
|
62
|
-
const contentState = await getProgressState(content.id)
|
|
63
|
-
if (contentState !== STATE_STARTED) {
|
|
64
|
-
nextLessonData[content.id] = children[0]
|
|
65
|
-
} else {
|
|
66
|
-
const childrenStates = await getProgressStateByIds(children)
|
|
67
|
-
|
|
68
|
-
//calculate last_engaged
|
|
69
|
-
const lastInteracted = await getLastInteractedOf(children)
|
|
70
|
-
const lastInteractedStatus = childrenStates[lastInteracted]
|
|
71
|
-
|
|
72
|
-
//different nextLesson behaviour for different content types
|
|
73
|
-
if (content.type === 'course' || content.type === 'pack-bundle' || content.type === 'skill-pack') {
|
|
74
|
-
if (lastInteractedStatus === STATE_STARTED) {
|
|
75
|
-
nextLessonData[content.id] = lastInteracted
|
|
76
|
-
} else {
|
|
77
|
-
nextLessonData[content.id] = findIncompleteLesson(
|
|
78
|
-
childrenStates,
|
|
79
|
-
lastInteracted,
|
|
80
|
-
content.type
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
} else if (content.type === 'guided-course' || content.type === 'song-tutorial') {
|
|
84
|
-
nextLessonData[content.id] = findIncompleteLesson(
|
|
85
|
-
childrenStates,
|
|
86
|
-
lastInteracted,
|
|
87
|
-
content.type
|
|
88
|
-
)
|
|
89
|
-
} else if (content.type === 'pack') {
|
|
90
|
-
const packBundles = content.children ?? []
|
|
91
|
-
const packBundleProgressData = await getNextLesson(packBundles)
|
|
92
|
-
const parentId = await getLastInteractedOf(packBundles.map((bundle) => bundle.id))
|
|
93
|
-
nextLessonData[content.id] = packBundleProgressData[parentId]
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return nextLessonData
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function getNavigateTo(data) {
|
|
54
|
+
export async function getNavigateTo(data, collection = null) {
|
|
102
55
|
let navigateToData = {}
|
|
103
|
-
|
|
56
|
+
|
|
57
|
+
const twoDepthContentTypes = ['pack'] // not adding method because it has its own logic (with active path)
|
|
104
58
|
//TODO add parent hierarchy upwards as well
|
|
105
59
|
// data structure is the same but instead of child{} we use parent{}
|
|
106
60
|
for (const content of data) {
|
|
@@ -125,31 +79,32 @@ export async function getNavigateTo(data) {
|
|
|
125
79
|
children.set(child.id, child)
|
|
126
80
|
})
|
|
127
81
|
// return first child (or grand child) if parent-content is complete or no progress
|
|
128
|
-
const contentState = await getProgressState(content.id)
|
|
82
|
+
const contentState = await getProgressState(content.id, collection)
|
|
129
83
|
if (contentState !== STATE_STARTED) {
|
|
130
84
|
const firstChild = validChildren[0]
|
|
131
85
|
let lastInteractedChildNavToData = await getNavigateTo([firstChild])
|
|
132
86
|
lastInteractedChildNavToData = lastInteractedChildNavToData[firstChild.id] ?? null
|
|
133
|
-
navigateToData[content.id] = buildNavigateTo(firstChild, lastInteractedChildNavToData)
|
|
87
|
+
navigateToData[content.id] = buildNavigateTo(firstChild, lastInteractedChildNavToData, collection) //no G-child for LP
|
|
134
88
|
} else {
|
|
135
|
-
const childrenStates = await getProgressStateByIds(childrenIds)
|
|
136
|
-
const lastInteracted = await getLastInteractedOf(childrenIds)
|
|
89
|
+
const childrenStates = await getProgressStateByIds(childrenIds, collection)
|
|
90
|
+
const lastInteracted = await getLastInteractedOf(childrenIds, collection)
|
|
137
91
|
const lastInteractedStatus = childrenStates[lastInteracted]
|
|
138
92
|
|
|
139
|
-
if (
|
|
140
|
-
if (lastInteractedStatus === STATE_STARTED) {
|
|
141
|
-
navigateToData[content.id] = buildNavigateTo(children.get(lastInteracted))
|
|
142
|
-
} else {
|
|
93
|
+
if (['course', 'pack-bundle', 'skill-pack'].includes(content.type)) {
|
|
94
|
+
if (lastInteractedStatus === STATE_STARTED) { // send to last interacted
|
|
95
|
+
navigateToData[content.id] = buildNavigateTo(children.get(lastInteracted), null, collection)
|
|
96
|
+
} else { // send to first incomplete after last interacted
|
|
143
97
|
let incompleteChild = findIncompleteLesson(childrenStates, lastInteracted, content.type)
|
|
144
|
-
navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild))
|
|
98
|
+
navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild), null, collection)
|
|
145
99
|
}
|
|
146
|
-
} else if (
|
|
100
|
+
} else if (['song-tutorial', 'guided-course', 'learning-path-v2'].includes(content.type)) { // send to first incomplete
|
|
147
101
|
let incompleteChild = findIncompleteLesson(childrenStates, lastInteracted, content.type)
|
|
148
|
-
navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild))
|
|
149
|
-
} else if (twoDepthContentTypes.includes(content.type)) {
|
|
102
|
+
navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild), null, collection)
|
|
103
|
+
} else if (twoDepthContentTypes.includes(content.type)) { // send to navigateTo child of last interacted child
|
|
150
104
|
const firstChildren = content.children ?? []
|
|
151
105
|
const lastInteractedChildId = await getLastInteractedOf(
|
|
152
|
-
firstChildren.map((child) => child.id)
|
|
106
|
+
firstChildren.map((child) => child.id),
|
|
107
|
+
collection
|
|
153
108
|
)
|
|
154
109
|
if (childrenStates[lastInteractedChildId] === STATE_COMPLETED) {
|
|
155
110
|
// TODO: packs have an extra situation where we need to jump to the next course if all lessons in the last engaged course are completed
|
|
@@ -158,7 +113,8 @@ export async function getNavigateTo(data) {
|
|
|
158
113
|
lastInteractedChildNavToData = lastInteractedChildNavToData[lastInteractedChildId]
|
|
159
114
|
navigateToData[content.id] = buildNavigateTo(
|
|
160
115
|
children.get(lastInteractedChildId),
|
|
161
|
-
lastInteractedChildNavToData
|
|
116
|
+
lastInteractedChildNavToData,
|
|
117
|
+
collection
|
|
162
118
|
)
|
|
163
119
|
}
|
|
164
120
|
}
|
|
@@ -167,7 +123,7 @@ export async function getNavigateTo(data) {
|
|
|
167
123
|
return navigateToData
|
|
168
124
|
}
|
|
169
125
|
|
|
170
|
-
function buildNavigateTo(content, child = null) {
|
|
126
|
+
function buildNavigateTo(content, child = null, collection = null) {
|
|
171
127
|
if (!content) {
|
|
172
128
|
return null
|
|
173
129
|
}
|
|
@@ -180,16 +136,18 @@ function buildNavigateTo(content, child = null) {
|
|
|
180
136
|
published_on: content.published_on ?? null,
|
|
181
137
|
status: content.status ?? '',
|
|
182
138
|
child: child,
|
|
139
|
+
collection: collection,
|
|
183
140
|
}
|
|
184
141
|
}
|
|
185
142
|
|
|
186
143
|
/**
|
|
187
144
|
* filter through contents, only keeping the most recent
|
|
188
145
|
* @param {array} contentIds
|
|
146
|
+
* @param {object|null} collection
|
|
189
147
|
* @returns {Promise<number>}
|
|
190
148
|
*/
|
|
191
|
-
export async function getLastInteractedOf(contentIds) {
|
|
192
|
-
const data = await getByIds(contentIds, DATA_KEY_LAST_UPDATED_TIME, 0)
|
|
149
|
+
export async function getLastInteractedOf(contentIds, collection = null) {
|
|
150
|
+
const data = await getByIds(contentIds, collection, DATA_KEY_LAST_UPDATED_TIME, 0)
|
|
193
151
|
const sorted = Object.keys(data)
|
|
194
152
|
.map(function (key) {
|
|
195
153
|
return parseInt(key)
|
|
@@ -205,40 +163,43 @@ export async function getLastInteractedOf(contentIds) {
|
|
|
205
163
|
return sorted[0]
|
|
206
164
|
}
|
|
207
165
|
|
|
208
|
-
export async function getProgressDateByIds(contentIds) {
|
|
166
|
+
export async function getProgressDateByIds(contentIds, collection = null) {
|
|
209
167
|
let data = await dataContext.getData()
|
|
210
168
|
let progress = {}
|
|
211
|
-
contentIds?.forEach(
|
|
212
|
-
(id)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
)
|
|
169
|
+
contentIds?.forEach((id) => {
|
|
170
|
+
const key = generateRecordKey(id, collection)
|
|
171
|
+
progress[id] = {
|
|
172
|
+
last_update: data[key]?.[DATA_KEY_LAST_UPDATED_TIME] ?? 0,
|
|
173
|
+
progress: data[key]?.[DATA_KEY_PROGRESS] ?? 0,
|
|
174
|
+
status: data[key]?.[DATA_KEY_STATUS] ?? '',
|
|
175
|
+
}
|
|
176
|
+
})
|
|
219
177
|
return progress
|
|
220
178
|
}
|
|
221
179
|
|
|
222
|
-
async function getById(contentId, dataKey, defaultValue) {
|
|
180
|
+
async function getById(contentId, collection, dataKey, defaultValue) {
|
|
223
181
|
let data = await dataContext.getData()
|
|
224
|
-
|
|
182
|
+
const contentKey = generateRecordKey(contentId, collection)
|
|
183
|
+
return data[contentKey]?.[dataKey] ?? defaultValue
|
|
225
184
|
}
|
|
226
185
|
|
|
227
|
-
async function getByIds(contentIds, dataKey, defaultValue) {
|
|
186
|
+
async function getByIds(contentIds, collection, dataKey, defaultValue) {
|
|
228
187
|
let data = await dataContext.getData()
|
|
229
188
|
let progress = {}
|
|
230
|
-
contentIds?.forEach((id) => (progress[id] = data[id]?.[dataKey] ?? defaultValue))
|
|
189
|
+
contentIds?.forEach((id) => (progress[id] = data[generateRecordKey(id, collection)]?.[dataKey] ?? defaultValue))
|
|
231
190
|
return progress
|
|
232
191
|
}
|
|
233
192
|
|
|
234
|
-
export async function getAllStarted(limit = null) {
|
|
193
|
+
export async function getAllStarted(limit = null, collection = null) {
|
|
235
194
|
const data = await dataContext.getData()
|
|
195
|
+
|
|
236
196
|
let ids = Object.keys(data)
|
|
237
|
-
.filter(function (
|
|
238
|
-
|
|
197
|
+
.filter(function (id) {
|
|
198
|
+
const key = generateRecordKey(id, collection)
|
|
199
|
+
return data[key][DATA_KEY_STATUS] === STATE_STARTED
|
|
239
200
|
})
|
|
240
|
-
.map(function (
|
|
241
|
-
return parseInt(
|
|
201
|
+
.map(function (id) {
|
|
202
|
+
return parseInt(id)
|
|
242
203
|
})
|
|
243
204
|
.sort(function (a, b) {
|
|
244
205
|
let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME]
|
|
@@ -253,14 +214,16 @@ export async function getAllStarted(limit = null) {
|
|
|
253
214
|
return ids
|
|
254
215
|
}
|
|
255
216
|
|
|
256
|
-
export async function getAllCompleted(limit = null) {
|
|
217
|
+
export async function getAllCompleted(limit = null, collection = null) {
|
|
257
218
|
const data = await dataContext.getData()
|
|
219
|
+
|
|
258
220
|
let ids = Object.keys(data)
|
|
259
|
-
.filter(function (
|
|
260
|
-
|
|
221
|
+
.filter(function (id) {
|
|
222
|
+
const key = generateRecordKey(id, collection)
|
|
223
|
+
return data[key][DATA_KEY_STATUS] === STATE_COMPLETED
|
|
261
224
|
})
|
|
262
|
-
.map(function (
|
|
263
|
-
return parseInt(
|
|
225
|
+
.map(function (id) {
|
|
226
|
+
return parseInt(id)
|
|
264
227
|
})
|
|
265
228
|
.sort(function (a, b) {
|
|
266
229
|
let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME]
|
|
@@ -280,21 +243,24 @@ export async function getAllStartedOrCompleted({
|
|
|
280
243
|
onlyIds = true,
|
|
281
244
|
brand = null,
|
|
282
245
|
excludedIds = [],
|
|
246
|
+
collection = null,
|
|
283
247
|
} = {}) {
|
|
284
248
|
const data = await dataContext.getData()
|
|
285
249
|
const oneMonthAgoInSeconds = Math.floor(Date.now() / 1000) - 60 * 24 * 60 * 60 // 60 days in seconds
|
|
286
250
|
|
|
287
251
|
const excludedSet = new Set(excludedIds.map((id) => parseInt(id))) // ensure IDs are numbers
|
|
288
|
-
|
|
289
252
|
let filtered = Object.entries(data)
|
|
290
253
|
.filter(([key, item]) => {
|
|
291
|
-
const id = parseInt(key)
|
|
292
254
|
const isRelevantStatus =
|
|
293
255
|
item[DATA_KEY_STATUS] === STATE_STARTED || item[DATA_KEY_STATUS] === STATE_COMPLETED
|
|
294
256
|
const isRecent = item[DATA_KEY_LAST_UPDATED_TIME] >= oneMonthAgoInSeconds
|
|
295
257
|
const isCorrectBrand = !brand || !item.b || item.b === brand
|
|
296
|
-
const isNotExcluded = !excludedSet.has(
|
|
297
|
-
|
|
258
|
+
const isNotExcluded = !excludedSet.has(extractContentIdFromRecordKey(key))
|
|
259
|
+
const matchesCollection =
|
|
260
|
+
(!collection && !item[DATA_KEY_COLLECTION]) ||
|
|
261
|
+
(item[DATA_KEY_COLLECTION]?.type === collection?.type &&
|
|
262
|
+
item[DATA_KEY_COLLECTION]?.id === collection?.id)
|
|
263
|
+
return matchesCollection && isRelevantStatus && isCorrectBrand && isNotExcluded
|
|
298
264
|
})
|
|
299
265
|
.sort(([, a], [, b]) => {
|
|
300
266
|
const v1 = a[DATA_KEY_LAST_UPDATED_TIME]
|
|
@@ -303,21 +269,28 @@ export async function getAllStartedOrCompleted({
|
|
|
303
269
|
else if (v1 < v2) return 1
|
|
304
270
|
return 0
|
|
305
271
|
})
|
|
272
|
+
//maps to content_id
|
|
273
|
+
.reduce((acc, [key, item]) => {
|
|
274
|
+
const newKey = extractContentIdFromRecordKey(key)
|
|
275
|
+
acc[newKey] = item
|
|
276
|
+
return acc
|
|
277
|
+
}, {})
|
|
306
278
|
|
|
307
279
|
if (limit) {
|
|
308
|
-
filtered = filtered.slice(0, limit)
|
|
280
|
+
filtered = Object.fromEntries(Object.entries(filtered).slice(0, limit))
|
|
309
281
|
}
|
|
310
282
|
|
|
311
283
|
if (onlyIds) {
|
|
312
|
-
return filtered.map(([key]) => parseInt(key))
|
|
284
|
+
return Object.entries(filtered).map(([key, data]) => parseInt(key))
|
|
313
285
|
} else {
|
|
314
286
|
const progress = {}
|
|
315
|
-
filtered.forEach(([key, item]) => {
|
|
287
|
+
Object.entries(filtered).forEach(([key, item]) => {
|
|
316
288
|
const id = parseInt(key)
|
|
317
289
|
progress[id] = {
|
|
318
290
|
last_update: item?.[DATA_KEY_LAST_UPDATED_TIME] ?? 0,
|
|
319
291
|
progress: item?.[DATA_KEY_PROGRESS] ?? 0,
|
|
320
292
|
status: item?.[DATA_KEY_STATUS] ?? '',
|
|
293
|
+
collection: item?.[DATA_KEY_COLLECTION],
|
|
321
294
|
brand: item?.b ?? '',
|
|
322
295
|
}
|
|
323
296
|
})
|
|
@@ -342,17 +315,24 @@ export async function getAllStartedOrCompleted({
|
|
|
342
315
|
* const progressMap = await getStartedOrCompletedProgressOnly({ brand: 'drumeo' });
|
|
343
316
|
* console.log(progressMap[123]); // => 52
|
|
344
317
|
*/
|
|
345
|
-
export async function getStartedOrCompletedProgressOnly({
|
|
318
|
+
export async function getStartedOrCompletedProgressOnly({
|
|
319
|
+
brand = null,
|
|
320
|
+
collection = null
|
|
321
|
+
} = {}) {
|
|
346
322
|
const data = await dataContext.getData()
|
|
347
323
|
const result = {}
|
|
348
324
|
|
|
349
325
|
Object.entries(data).forEach(([key, item]) => {
|
|
350
|
-
const id =
|
|
326
|
+
const id = extractContentIdFromRecordKey(key)
|
|
351
327
|
const isRelevantStatus =
|
|
352
328
|
item[DATA_KEY_STATUS] === STATE_STARTED || item[DATA_KEY_STATUS] === STATE_COMPLETED
|
|
353
329
|
const isCorrectBrand = !brand || item.b === brand
|
|
330
|
+
const matchesCollection =
|
|
331
|
+
(!collection && !item[DATA_KEY_COLLECTION]) ||
|
|
332
|
+
(item[DATA_KEY_COLLECTION]?.type === collection?.type &&
|
|
333
|
+
item[DATA_KEY_COLLECTION]?.id === collection?.id)
|
|
354
334
|
|
|
355
|
-
if (isRelevantStatus && isCorrectBrand) {
|
|
335
|
+
if (matchesCollection && isRelevantStatus && isCorrectBrand) {
|
|
356
336
|
result[id] = item?.[DATA_KEY_PROGRESS] ?? 0
|
|
357
337
|
}
|
|
358
338
|
})
|
|
@@ -360,36 +340,37 @@ export async function getStartedOrCompletedProgressOnly({ brand = null } = {}) {
|
|
|
360
340
|
return result
|
|
361
341
|
}
|
|
362
342
|
|
|
363
|
-
export async function contentStatusCompleted(contentId) {
|
|
343
|
+
export async function contentStatusCompleted(contentId, collection = null) {
|
|
364
344
|
return await dataContext.update(
|
|
365
345
|
async function (localContext) {
|
|
366
|
-
let hierarchy = await
|
|
367
|
-
completeStatusInLocalContext(localContext, contentId, hierarchy)
|
|
346
|
+
let hierarchy = await getContentHierarchy(contentId, collection)
|
|
347
|
+
completeStatusInLocalContext(localContext, contentId, hierarchy, collection)
|
|
368
348
|
},
|
|
369
349
|
async function () {
|
|
370
|
-
return postContentComplete(contentId)
|
|
350
|
+
return postContentComplete(contentId, collection)
|
|
371
351
|
}
|
|
372
352
|
)
|
|
373
353
|
}
|
|
374
|
-
export async function contentStatusStarted(contentId) {
|
|
354
|
+
export async function contentStatusStarted(contentId, collection = null) {
|
|
375
355
|
return await dataContext.update(
|
|
376
356
|
async function (localContext) {
|
|
377
|
-
let hierarchy = await
|
|
378
|
-
startStatusInLocalContext(localContext, contentId, hierarchy)
|
|
357
|
+
let hierarchy = await getContentHierarchy(contentId, collection)
|
|
358
|
+
startStatusInLocalContext(localContext, contentId, hierarchy, collection)
|
|
379
359
|
},
|
|
380
360
|
async function () {
|
|
381
|
-
return postContentStart(contentId)
|
|
361
|
+
return postContentStart(contentId, collection)
|
|
382
362
|
}
|
|
383
363
|
)
|
|
384
364
|
}
|
|
385
365
|
|
|
386
|
-
function saveContentProgress(localContext, contentId, progress, currentSeconds, hierarchy) {
|
|
366
|
+
function saveContentProgress(localContext, contentId, progress, currentSeconds, hierarchy, collection = null) {
|
|
387
367
|
if (progress === 100) {
|
|
388
|
-
completeStatusInLocalContext(localContext, contentId, hierarchy)
|
|
368
|
+
completeStatusInLocalContext(localContext, contentId, hierarchy, collection)
|
|
389
369
|
return
|
|
390
370
|
}
|
|
391
371
|
|
|
392
|
-
|
|
372
|
+
const key = generateRecordKey(contentId, collection)
|
|
373
|
+
let data = localContext.data[key] ?? {}
|
|
393
374
|
const currentProgress = data[DATA_KEY_STATUS]
|
|
394
375
|
if (!currentProgress || currentProgress !== STATE_COMPLETED) {
|
|
395
376
|
data[DATA_KEY_PROGRESS] = progress
|
|
@@ -397,32 +378,43 @@ function saveContentProgress(localContext, contentId, progress, currentSeconds,
|
|
|
397
378
|
}
|
|
398
379
|
data[DATA_KEY_RESUME_TIME] = currentSeconds
|
|
399
380
|
data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000)
|
|
400
|
-
localContext.data[
|
|
381
|
+
localContext.data[key] = data
|
|
401
382
|
|
|
402
383
|
bubbleProgress(hierarchy, contentId, localContext)
|
|
403
384
|
}
|
|
404
385
|
|
|
405
|
-
function completeStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
406
|
-
setStartedOrCompletedStatusInLocalContext(localContext, contentId, true, hierarchy)
|
|
386
|
+
function completeStatusInLocalContext(localContext, contentId, hierarchy, collection = null) {
|
|
387
|
+
setStartedOrCompletedStatusInLocalContext(localContext, contentId, true, hierarchy, collection)
|
|
407
388
|
}
|
|
408
389
|
|
|
409
|
-
function startStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
410
|
-
setStartedOrCompletedStatusInLocalContext(localContext, contentId, false, hierarchy)
|
|
390
|
+
function startStatusInLocalContext(localContext, contentId, hierarchy, collection = null) {
|
|
391
|
+
setStartedOrCompletedStatusInLocalContext(localContext, contentId, false, hierarchy, collection)
|
|
411
392
|
}
|
|
412
393
|
|
|
413
394
|
function setStartedOrCompletedStatusInLocalContext(
|
|
414
395
|
localContext,
|
|
415
396
|
contentId,
|
|
416
397
|
isCompleted,
|
|
417
|
-
hierarchy
|
|
398
|
+
hierarchy,
|
|
399
|
+
collection = null
|
|
418
400
|
) {
|
|
419
|
-
|
|
401
|
+
const key = generateRecordKey(contentId, collection)
|
|
402
|
+
let data = localContext.data[key] ?? {}
|
|
420
403
|
data[DATA_KEY_PROGRESS] = isCompleted ? 100 : 0
|
|
421
404
|
data[DATA_KEY_STATUS] = isCompleted ? STATE_COMPLETED : STATE_STARTED
|
|
422
405
|
data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000)
|
|
423
|
-
|
|
406
|
+
data[DATA_KEY_COLLECTION] = collection
|
|
407
|
+
data[DATA_CONTENT_ID] = contentId
|
|
408
|
+
|
|
409
|
+
localContext.data[key] = data
|
|
424
410
|
|
|
425
411
|
if (!hierarchy) return
|
|
412
|
+
|
|
413
|
+
if (collection && collection.type === 'learning-path') {
|
|
414
|
+
bubbleOrTrickleLearningPathProgress(hierarchy, contentId, localContext, isCompleted, collection)
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
426
418
|
let children = hierarchy.children[contentId] ?? []
|
|
427
419
|
for (let i = 0; i < children.length; i++) {
|
|
428
420
|
let childId = children[i]
|
|
@@ -440,29 +432,52 @@ function getChildrenToDepth(parentId, hierarchy, depth = 1) {
|
|
|
440
432
|
return allChildrenIds
|
|
441
433
|
}
|
|
442
434
|
|
|
443
|
-
export async function contentStatusReset(contentId) {
|
|
435
|
+
export async function contentStatusReset(contentId, collection = null) {
|
|
444
436
|
await dataContext.update(
|
|
445
437
|
async function (localContext) {
|
|
446
|
-
let hierarchy = await
|
|
447
|
-
resetStatusInLocalContext(localContext, contentId, hierarchy)
|
|
438
|
+
let hierarchy = await getContentHierarchy(contentId, collection)
|
|
439
|
+
resetStatusInLocalContext(localContext, contentId, hierarchy, collection)
|
|
448
440
|
},
|
|
449
441
|
async function () {
|
|
450
|
-
return postContentReset(contentId)
|
|
442
|
+
return postContentReset(contentId, collection)
|
|
451
443
|
}
|
|
452
444
|
)
|
|
453
445
|
}
|
|
454
446
|
|
|
455
|
-
function resetStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
456
|
-
let
|
|
457
|
-
|
|
447
|
+
function resetStatusInLocalContext(localContext, contentId, hierarchy, collection = null) {
|
|
448
|
+
let keys = []
|
|
449
|
+
|
|
450
|
+
console.log('all', [localContext, contentId, hierarchy, collection])
|
|
451
|
+
keys.push(generateRecordKey(contentId, collection))
|
|
452
|
+
|
|
453
|
+
let allChildIds
|
|
454
|
+
let learningPathId = null
|
|
455
|
+
let childrenIds = []
|
|
456
|
+
if (collection && collection.type === 'learning-path') {
|
|
457
|
+
[learningPathId, childrenIds] = findLearningPathAndChildren(hierarchy, contentId)
|
|
458
|
+
allChildIds = (learningPathId === contentId) ? childrenIds : []
|
|
459
|
+
} else {
|
|
460
|
+
allChildIds = getChildrenToDepth(contentId, hierarchy, 5)
|
|
461
|
+
}
|
|
462
|
+
|
|
458
463
|
allChildIds.forEach((id) => {
|
|
459
|
-
|
|
464
|
+
keys.push(generateRecordKey(id, collection))
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
keys.forEach((key) => {
|
|
468
|
+
const index = Object.keys(localContext.data).indexOf(key.toString())
|
|
460
469
|
if (index > -1) {
|
|
461
470
|
// only splice array when item is found
|
|
462
|
-
delete localContext.data[
|
|
471
|
+
delete localContext.data[key]
|
|
463
472
|
}
|
|
464
473
|
})
|
|
465
|
-
|
|
474
|
+
if (collection && collection.type === 'learning-path') { // manual bubbling for LP
|
|
475
|
+
if (learningPathId !== contentId) {
|
|
476
|
+
bubbleLearningPathProgress(hierarchy, contentId, localContext, collection)
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
bubbleProgress(hierarchy, contentId, localContext)
|
|
480
|
+
}
|
|
466
481
|
}
|
|
467
482
|
|
|
468
483
|
/**
|
|
@@ -477,7 +492,9 @@ function resetStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
|
477
492
|
* @param {string} sessionId - This function records a sessionId to pass into future updates to progress on the same video
|
|
478
493
|
* @param {int} instrumentId - enum value of instrument id
|
|
479
494
|
* @param {int} categoryId - enum value of category id
|
|
495
|
+
* @param {object|null} collection - optional collection info { type: 'learning-path', id: 123 }
|
|
480
496
|
*/
|
|
497
|
+
// NOTE: have not set up collection because its not super important for testing and this will change soon with watermelon
|
|
481
498
|
export async function recordWatchSession(
|
|
482
499
|
contentId,
|
|
483
500
|
mediaType,
|
|
@@ -487,8 +504,14 @@ export async function recordWatchSession(
|
|
|
487
504
|
secondsPlayed,
|
|
488
505
|
sessionId = null,
|
|
489
506
|
instrumentId = null,
|
|
490
|
-
categoryId = null
|
|
507
|
+
categoryId = null,
|
|
508
|
+
collection = null
|
|
491
509
|
) {
|
|
510
|
+
if (collection && collection.type === 'learning-path') {
|
|
511
|
+
console.log('Learning Path lesson watch sessions are not set up yet without watermelon')
|
|
512
|
+
return sessionId
|
|
513
|
+
}
|
|
514
|
+
|
|
492
515
|
let mediaTypeId = getMediaTypeId(mediaType, mediaCategory)
|
|
493
516
|
let updateLocalProgress = mediaTypeId === 1 || mediaTypeId === 2 //only update for video playback
|
|
494
517
|
if (!sessionId) {
|
|
@@ -580,3 +603,83 @@ function bubbleProgress(hierarchy, contentId, localContext) {
|
|
|
580
603
|
localContext.data[parentId] = data
|
|
581
604
|
bubbleProgress(hierarchy, parentId, localContext)
|
|
582
605
|
}
|
|
606
|
+
|
|
607
|
+
function bubbleLearningPathProgress(hierarchy, contentId, localContext, collection) {
|
|
608
|
+
const [parentId, childrenIds] = findLearningPathAndChildren(hierarchy, contentId)
|
|
609
|
+
|
|
610
|
+
if (!parentId || parentId === contentId) return
|
|
611
|
+
|
|
612
|
+
const parentKey = generateRecordKey(parentId, collection)
|
|
613
|
+
let data = localContext.data[parentKey] ?? {}
|
|
614
|
+
|
|
615
|
+
let childProgress = childrenIds.map(function (childId) {
|
|
616
|
+
const childKey = generateRecordKey(childId, collection)
|
|
617
|
+
return localContext.data[childKey]?.[DATA_KEY_PROGRESS] ?? 0
|
|
618
|
+
})
|
|
619
|
+
let progress = Math.round(childProgress.reduce((a, b) => a + b, 0) / childProgress.length)
|
|
620
|
+
|
|
621
|
+
const contentKey = generateRecordKey(contentId, collection)
|
|
622
|
+
const brand = localContext.data[contentKey]?.[DATA_KEY_BRAND] ?? null
|
|
623
|
+
|
|
624
|
+
data[DATA_KEY_PROGRESS] = progress
|
|
625
|
+
data[DATA_KEY_STATUS] = progress === 100 ? STATE_COMPLETED : STATE_STARTED
|
|
626
|
+
data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000)
|
|
627
|
+
data[DATA_KEY_BRAND] = brand
|
|
628
|
+
data[DATA_KEY_COLLECTION] = collection
|
|
629
|
+
data[DATA_CONTENT_ID] = parentId
|
|
630
|
+
|
|
631
|
+
localContext.data[parentKey] = data
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function generateRecordKey(contentId, collection) {
|
|
635
|
+
return collection ? `${contentId}:${collection.type}:${collection.id}` : contentId
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function extractContentIdFromRecordKey(key) {
|
|
639
|
+
return parseInt(key.split(':')[0])
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function getContentHierarchy(contentId, collection = null) {
|
|
643
|
+
if (collection && collection.type === 'learning-path') {
|
|
644
|
+
return fetchMethodV2StructureFromId(contentId)
|
|
645
|
+
}
|
|
646
|
+
return await fetchHierarchy(contentId)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function findLearningPathAndChildren(data, contentId) {
|
|
650
|
+
let learningPathId = null
|
|
651
|
+
let children = []
|
|
652
|
+
|
|
653
|
+
if (!data?.learningPaths) return [ learningPathId, children ]
|
|
654
|
+
|
|
655
|
+
for (const lp of data.learningPaths) {
|
|
656
|
+
if (lp.id === contentId) {
|
|
657
|
+
learningPathId = contentId
|
|
658
|
+
children = lp.children ?? []
|
|
659
|
+
break
|
|
660
|
+
}
|
|
661
|
+
if (Array.isArray(lp.children) && lp.children.includes(contentId)) {
|
|
662
|
+
learningPathId = lp.id
|
|
663
|
+
children = lp.children ?? []
|
|
664
|
+
break
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return [learningPathId, children]
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function bubbleOrTrickleLearningPathProgress(hierarchy, contentId, localContext, isCompleted, collection) {
|
|
672
|
+
const [parentId, childrenIds] = findLearningPathAndChildren(hierarchy, contentId)
|
|
673
|
+
|
|
674
|
+
if (parentId !== contentId) { // if contentId is not a learning path
|
|
675
|
+
bubbleLearningPathProgress(hierarchy, contentId, localContext, collection)
|
|
676
|
+
return
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (childrenIds) {
|
|
680
|
+
for (let i = 0; i < childrenIds.length; i++) {
|
|
681
|
+
let childId = childrenIds[i]
|
|
682
|
+
setStartedOrCompletedStatusInLocalContext(localContext, childId, isCompleted, null, collection)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|