musora-content-services 2.158.3 → 2.160.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/.github/workflows/automated-testing.yml +21 -1
- package/CHANGELOG.md +16 -0
- package/README.md +21 -2
- package/jest.config.js +1 -4
- package/jest.integration.config.js +6 -0
- package/jest.live.config.js +1 -5
- package/package.json +5 -2
- package/src/contentTypeConfig.js +8 -5
- package/src/index.d.ts +2 -6
- package/src/index.js +2 -6
- package/src/services/content-org/learning-paths.ts +44 -39
- package/src/services/contentProgress.js +216 -207
- package/src/services/offline/progress.ts +107 -27
- package/src/services/sanity.js +55 -64
- package/src/services/sync/manager.ts +5 -0
- package/src/services/sync/models/ContentProgress.ts +50 -34
- package/src/services/sync/repositories/content-progress.ts +105 -92
- package/src/services/sync/stale-record-cleanup.ts +66 -0
- package/test/{unit → integration}/awards/award-exclusion-handling.test.ts +2 -2
- package/test/integration/content-progress/__mocks__/mocks.ts +104 -0
- package/test/integration/content-progress/contentProgress.test.ts +335 -0
- package/test/integration/content-progress/e2eOfflineProgress.test.ts +352 -0
- package/test/integration/content-progress/e2eProgress.test.ts +612 -0
- package/test/integration/content-progress/getters.test.ts +334 -0
- package/test/integration/content-progress/helpers.test.ts +263 -0
- package/test/integration/content-progress/offlineContentProgress.test.ts +226 -0
- package/test/integration/forums.test.ts +209 -0
- package/test/integration/initializeTestDB.ts +80 -0
- package/test/{unit → integration}/sync/fetch.test.ts +1 -1
- package/test/{unit → integration}/sync/repositories/content-likes.test.ts +1 -1
- package/test/{unit → integration}/sync/repositories/practices.test.ts +1 -1
- package/test/{unit → integration}/sync/repositories/progress.test.ts +1 -1
- package/test/{unit → integration}/sync/repositories/user-award-progress.test.ts +1 -1
- package/test/{unit → integration}/sync/store/cross-user-protection.test.ts +2 -2
- package/test/{unit → integration}/sync/store/store-idb.test.ts +2 -2
- package/test/{unit → integration}/sync/store/store.test.ts +2 -2
- package/test/unit/content-progress/bubbleTrickle.test.ts +322 -0
- package/test/unit/content-progress/helpers.test.ts +329 -0
- package/test/unit/content-progress/navigateTo.test.ts +381 -0
- package/test/unit/contentMetaData.test.ts +58 -0
- package/test/unit/sync/stale-record-cleanup.test.ts +107 -0
- package/tools/generate-index.cjs +6 -3
- package/test/SKIPPED_TESTS.md +0 -151
- package/test/integration/content.test.js +0 -107
- package/test/integration/contentProgress.test.js +0 -73
- package/test/integration/forum.test.js +0 -16
- package/test/integration/sanityQueryService.test.js +0 -681
- package/test/unit/contentProgress.test.ts +0 -81
- /package/test/{unit → integration}/awards/internal/image-utils.test.ts +0 -0
- /package/test/{unit → integration}/infrastructure/FetchRequestExecutor.test.ts +0 -0
- /package/test/{unit → integration}/notifications.test.ts +0 -0
- /package/test/{unit → integration}/sync/adapters/idb-errors.test.ts +0 -0
- /package/test/{unit → integration}/sync/adapters/sqlite-errors.test.ts +0 -0
- /package/test/{unit → integration}/sync/repositories/user-award-progress.static.test.ts +0 -0
- /package/test/{unit → integration}/userActivity.test.ts +0 -0
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
- uses: actions/checkout@v4
|
|
13
13
|
- uses: actions/setup-node@v4
|
|
14
14
|
with:
|
|
15
|
-
node-version:
|
|
15
|
+
node-version: 22
|
|
16
16
|
cache: npm
|
|
17
17
|
- name: Install dependencies
|
|
18
18
|
run: npm ci
|
|
@@ -25,3 +25,23 @@ jobs:
|
|
|
25
25
|
slug: railroadmedia/musora-content-services
|
|
26
26
|
flags: unit
|
|
27
27
|
fail_ci_if_error: true
|
|
28
|
+
integration-tests:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
timeout-minutes: 5
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: actions/setup-node@v4
|
|
34
|
+
with:
|
|
35
|
+
node-version: 22
|
|
36
|
+
cache: npm
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: npm ci
|
|
39
|
+
- name: Run integration tests
|
|
40
|
+
run: npm run test:integration
|
|
41
|
+
- name: Upload coverage to Codecov
|
|
42
|
+
uses: codecov/codecov-action@v5
|
|
43
|
+
with:
|
|
44
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
45
|
+
slug: railroadmedia/musora-content-services
|
|
46
|
+
flags: integration
|
|
47
|
+
fail_ci_if_error: true
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.160.0](https://github.com/railroadmedia/musora-content-services/compare/v2.159.0...v2.160.0) (2026-05-13)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* adds stale record cleanup ([#963](https://github.com/railroadmedia/musora-content-services/issues/963)) ([72d917f](https://github.com/railroadmedia/musora-content-services/commit/72d917ff0d64d8c27f1d4ec831233c17663b1c49))
|
|
11
|
+
* use client-side filtering for live events to leverage sanity cache ([#930](https://github.com/railroadmedia/musora-content-services/issues/930)) ([cbfe024](https://github.com/railroadmedia/musora-content-services/commit/cbfe02476b654a240bb1ba35093a98a03535227c))
|
|
12
|
+
|
|
13
|
+
## [2.159.0](https://github.com/railroadmedia/musora-content-services/compare/v2.158.3...v2.159.0) (2026-05-12)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **BEHSTP-304:** LP lesson downloads & refactors ([#955](https://github.com/railroadmedia/musora-content-services/issues/955)) ([536e90f](https://github.com/railroadmedia/musora-content-services/commit/536e90f2b7e4d405e22b3de497191aa5c61b5776))
|
|
19
|
+
* **TP-1267:** audit live tests ([#962](https://github.com/railroadmedia/musora-content-services/issues/962)) ([aff540e](https://github.com/railroadmedia/musora-content-services/commit/aff540ec2e2f6f083e03406bc592eb54b4be8148))
|
|
20
|
+
|
|
5
21
|
### [2.158.3](https://github.com/railroadmedia/musora-content-services/compare/v2.158.2...v2.158.3) (2026-05-07)
|
|
6
22
|
|
|
7
23
|
|
package/README.md
CHANGED
|
@@ -120,8 +120,27 @@ https://railroadmedia.github.io/musora-content-services/
|
|
|
120
120
|
## Run tests
|
|
121
121
|
Ensure that the setup process has been completed, including copying .env file from 1Password "musora-content-services .env"
|
|
122
122
|
and having jest installed (`npm install --save-dev jest`). To run the full test suite, simply run the following:
|
|
123
|
+
|
|
124
|
+
### Unit tests only (fast, no external dependencies)
|
|
123
125
|
```
|
|
124
126
|
npm test
|
|
125
127
|
```
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
### Integration tests only (mocked external boundaries, runs in CI)
|
|
129
|
+
```
|
|
130
|
+
npm run test:integration
|
|
131
|
+
```
|
|
132
|
+
### Unit + integration (both suites)
|
|
133
|
+
```
|
|
134
|
+
npm run test:all
|
|
135
|
+
```
|
|
136
|
+
### Live tests (requires real credentials in .env, never runs in CI)
|
|
137
|
+
```
|
|
138
|
+
npm run test:live
|
|
139
|
+
```
|
|
140
|
+
### Filter to a specific test
|
|
141
|
+
```
|
|
142
|
+
npm test -- -t="contentLiked"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Without coverage
|
|
146
|
+
add flag `--coverage=false`
|
package/jest.config.js
CHANGED
|
@@ -117,10 +117,7 @@ export default {
|
|
|
117
117
|
},
|
|
118
118
|
|
|
119
119
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
|
120
|
-
|
|
121
|
-
'<rootDir>/test/live',
|
|
122
|
-
'<rootDir>/test/integration'
|
|
123
|
-
],
|
|
120
|
+
testMatch: ['<rootDir>/test/unit/**/*.test.[jt]s?(x)'],
|
|
124
121
|
|
|
125
122
|
// Activates notifications for test results
|
|
126
123
|
// notify: false,
|
package/jest.live.config.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
/** @type {import('jest').Config} */
|
|
2
1
|
import baseConfig from './jest.config.js'
|
|
3
|
-
|
|
4
2
|
export default {
|
|
5
3
|
...baseConfig,
|
|
6
|
-
|
|
7
|
-
setupFilesAfterEnv: ['dotenv/config'],
|
|
8
|
-
testTimeout: 1000000,
|
|
4
|
+
testMatch: ['<rootDir>/test/live/**/*.test.[jt]s?(x)'],
|
|
9
5
|
collectCoverage: false,
|
|
10
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musora-content-services",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.160.0",
|
|
4
4
|
"description": "A package for Musoras content services ",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"build-index": "node tools/generate-index.cjs",
|
|
9
9
|
"release": "standard-version",
|
|
10
10
|
"doc": "jsdoc -c jsdoc.json --verbose",
|
|
11
|
-
"test": "jest"
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"test:integration": "jest --config=jest.integration.config.js",
|
|
13
|
+
"test:all": "jest --config=jest.config.js; jest --config=jest.integration.config.js",
|
|
14
|
+
"test:live": "jest --config=jest.live.config.js"
|
|
12
15
|
},
|
|
13
16
|
"repository": {
|
|
14
17
|
"type": "git",
|
package/src/contentTypeConfig.js
CHANGED
|
@@ -437,7 +437,7 @@ export let contentTypeConfig = {
|
|
|
437
437
|
'railcontent_id',
|
|
438
438
|
'"assignments": assignment[]{railcontent_id}',
|
|
439
439
|
`"metadata": { brand, "type": _type, "parent_id": coalesce(${parentReferenceField}->railcontent_id, 0) }`,
|
|
440
|
-
|
|
440
|
+
],
|
|
441
441
|
childFields: [
|
|
442
442
|
'railcontent_id',
|
|
443
443
|
'"assignments": assignment[]{railcontent_id}',
|
|
@@ -498,6 +498,7 @@ export let contentTypeConfig = {
|
|
|
498
498
|
'course-lesson': {
|
|
499
499
|
fields: [`"resources": ${resourcesField}`],
|
|
500
500
|
},
|
|
501
|
+
|
|
501
502
|
download: {
|
|
502
503
|
fields: [
|
|
503
504
|
`"resources": ${resourcesField}`,
|
|
@@ -512,6 +513,7 @@ export let contentTypeConfig = {
|
|
|
512
513
|
'video',
|
|
513
514
|
...playAlongMp3sFields,
|
|
514
515
|
pcdForDownloadField,
|
|
516
|
+
'"learning_path_parent_id": *[_type == "learning-path-v2" && references(^._id)][0].railcontent_id',
|
|
515
517
|
`...select(
|
|
516
518
|
defined(live_event_start_time) => {
|
|
517
519
|
${getLiveFields(true).join(',')}
|
|
@@ -531,6 +533,7 @@ export let contentTypeConfig = {
|
|
|
531
533
|
'video',
|
|
532
534
|
...playAlongMp3sFields,
|
|
533
535
|
pcdForDownloadField,
|
|
536
|
+
'"learning_path_parent_id": *[_type == "learning-path-v2" && references(^._id)][0].railcontent_id',
|
|
534
537
|
`...select(
|
|
535
538
|
defined(live_event_start_time) => {
|
|
536
539
|
${getLiveFields(true).join(',')}
|
|
@@ -693,7 +696,7 @@ export let contentTypeConfig = {
|
|
|
693
696
|
'new-and-scheduled': {
|
|
694
697
|
fields: [
|
|
695
698
|
'show_in_new_feed',
|
|
696
|
-
isLiveField()
|
|
699
|
+
isLiveField(),
|
|
697
700
|
],
|
|
698
701
|
},
|
|
699
702
|
}
|
|
@@ -812,7 +815,7 @@ export function artistOrInstructorNameAsArray(key = 'artists') {
|
|
|
812
815
|
|
|
813
816
|
export async function getFieldsForContentTypeWithFilteredChildren(
|
|
814
817
|
contentType,
|
|
815
|
-
asQueryString = true
|
|
818
|
+
asQueryString = true,
|
|
816
819
|
) {
|
|
817
820
|
const childFields = getChildFieldsForContentType(contentType, true)
|
|
818
821
|
const parentFields = getFieldsForContentType(contentType, false)
|
|
@@ -827,7 +830,7 @@ export async function getFieldsForContentTypeWithFilteredChildren(
|
|
|
827
830
|
"children": child[${childFilter}]->{
|
|
828
831
|
${childFields}
|
|
829
832
|
},
|
|
830
|
-
}
|
|
833
|
+
}`,
|
|
831
834
|
)
|
|
832
835
|
}
|
|
833
836
|
return asQueryString ? parentFields.toString() + ',' : parentFields
|
|
@@ -917,7 +920,7 @@ const filterHandlers = {
|
|
|
917
920
|
length: (value) => {
|
|
918
921
|
// Find the matching length option by name
|
|
919
922
|
const lengthOption = Object.values(LengthFilterOptions).find(
|
|
920
|
-
(opt) => typeof opt === 'object' && opt.name === value
|
|
923
|
+
(opt) => typeof opt === 'object' && opt.name === value,
|
|
921
924
|
)
|
|
922
925
|
|
|
923
926
|
if (!lengthOption) return ''
|
package/src/index.d.ts
CHANGED
|
@@ -57,9 +57,6 @@ import {
|
|
|
57
57
|
getEnrichedLearningPaths,
|
|
58
58
|
getLearningPathLessonsByIds,
|
|
59
59
|
mapContentToParent,
|
|
60
|
-
mapContentsThatWereLastProgressedFromMethod,
|
|
61
|
-
mapLearningPathParentsTo,
|
|
62
|
-
onContentCompletedLearningPathActions,
|
|
63
60
|
resetAllLearningPaths,
|
|
64
61
|
startLearningPath,
|
|
65
62
|
updateDailySession
|
|
@@ -446,6 +443,7 @@ import {
|
|
|
446
443
|
|
|
447
444
|
import {
|
|
448
445
|
getOnboardingRecommendedContent,
|
|
446
|
+
initializeOnboardingFlow,
|
|
449
447
|
startOnboarding,
|
|
450
448
|
updateOnboarding,
|
|
451
449
|
userOnboardingForBrand
|
|
@@ -727,6 +725,7 @@ declare module 'musora-content-services' {
|
|
|
727
725
|
guidedCourses,
|
|
728
726
|
hasAnyMethodV2IntroCompleted,
|
|
729
727
|
initializeEnvVar,
|
|
728
|
+
initializeOnboardingFlow,
|
|
730
729
|
initializeService,
|
|
731
730
|
isBucketUrl,
|
|
732
731
|
isContentLiked,
|
|
@@ -745,8 +744,6 @@ declare module 'musora-content-services' {
|
|
|
745
744
|
login,
|
|
746
745
|
logout,
|
|
747
746
|
mapContentToParent,
|
|
748
|
-
mapContentsThatWereLastProgressedFromMethod,
|
|
749
|
-
mapLearningPathParentsTo,
|
|
750
747
|
markAllNotificationsAsRead,
|
|
751
748
|
markContentAsInterested,
|
|
752
749
|
markContentAsNotInterested,
|
|
@@ -754,7 +751,6 @@ declare module 'musora-content-services' {
|
|
|
754
751
|
markNotificationAsUnread,
|
|
755
752
|
markThreadAsRead,
|
|
756
753
|
numberOfActiveUsers,
|
|
757
|
-
onContentCompletedLearningPathActions,
|
|
758
754
|
onProgressSaved,
|
|
759
755
|
openComment,
|
|
760
756
|
otherStats,
|
package/src/index.js
CHANGED
|
@@ -61,9 +61,6 @@ import {
|
|
|
61
61
|
getEnrichedLearningPaths,
|
|
62
62
|
getLearningPathLessonsByIds,
|
|
63
63
|
mapContentToParent,
|
|
64
|
-
mapContentsThatWereLastProgressedFromMethod,
|
|
65
|
-
mapLearningPathParentsTo,
|
|
66
|
-
onContentCompletedLearningPathActions,
|
|
67
64
|
resetAllLearningPaths,
|
|
68
65
|
startLearningPath,
|
|
69
66
|
updateDailySession
|
|
@@ -450,6 +447,7 @@ import {
|
|
|
450
447
|
|
|
451
448
|
import {
|
|
452
449
|
getOnboardingRecommendedContent,
|
|
450
|
+
initializeOnboardingFlow,
|
|
453
451
|
startOnboarding,
|
|
454
452
|
updateOnboarding,
|
|
455
453
|
userOnboardingForBrand
|
|
@@ -726,6 +724,7 @@ export {
|
|
|
726
724
|
guidedCourses,
|
|
727
725
|
hasAnyMethodV2IntroCompleted,
|
|
728
726
|
initializeEnvVar,
|
|
727
|
+
initializeOnboardingFlow,
|
|
729
728
|
initializeService,
|
|
730
729
|
isBucketUrl,
|
|
731
730
|
isContentLiked,
|
|
@@ -744,8 +743,6 @@ export {
|
|
|
744
743
|
login,
|
|
745
744
|
logout,
|
|
746
745
|
mapContentToParent,
|
|
747
|
-
mapContentsThatWereLastProgressedFromMethod,
|
|
748
|
-
mapLearningPathParentsTo,
|
|
749
746
|
markAllNotificationsAsRead,
|
|
750
747
|
markContentAsInterested,
|
|
751
748
|
markContentAsNotInterested,
|
|
@@ -753,7 +750,6 @@ export {
|
|
|
753
750
|
markNotificationAsUnread,
|
|
754
751
|
markThreadAsRead,
|
|
755
752
|
numberOfActiveUsers,
|
|
756
|
-
onContentCompletedLearningPathActions,
|
|
757
753
|
onProgressSaved,
|
|
758
754
|
openComment,
|
|
759
755
|
otherStats,
|
|
@@ -26,10 +26,16 @@ import { ContentProgress } from '../sync/models'
|
|
|
26
26
|
import dayjs from 'dayjs'
|
|
27
27
|
import { LEARNING_PATH_LESSON } from '../../contentTypeConfig'
|
|
28
28
|
|
|
29
|
+
const excludeFromGeneratedIndex = [
|
|
30
|
+
'onLearningPathCompletedActions',
|
|
31
|
+
'mapContentsThatWereLastProgressedFromMethod',
|
|
32
|
+
'mapLearningPathParentsTo',
|
|
33
|
+
]
|
|
34
|
+
|
|
29
35
|
const BASE_PATH: string = `/api/content-org`
|
|
30
36
|
const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
|
|
31
|
-
let dailySessionPromise: Promise<DailySessionResponse|
|
|
32
|
-
let activePathPromise: Promise<ActiveLearningPathResponse|
|
|
37
|
+
let dailySessionPromise: Promise<DailySessionResponse | ''> | null = null
|
|
38
|
+
let activePathPromise: Promise<ActiveLearningPathResponse | ''> | null = null
|
|
33
39
|
|
|
34
40
|
interface ActiveLearningPathResponse {
|
|
35
41
|
user_id: number
|
|
@@ -66,7 +72,7 @@ interface CollectionObject {
|
|
|
66
72
|
export async function getDailySession(
|
|
67
73
|
brand: string,
|
|
68
74
|
userDate: Date,
|
|
69
|
-
forceRefresh: boolean = false
|
|
75
|
+
forceRefresh: boolean = false,
|
|
70
76
|
) {
|
|
71
77
|
if (dailySessionPromise && !forceRefresh) {
|
|
72
78
|
return dailySessionPromise
|
|
@@ -105,7 +111,7 @@ export async function getDailySession(
|
|
|
105
111
|
export async function updateDailySession(
|
|
106
112
|
brand: string,
|
|
107
113
|
userDate: Date,
|
|
108
|
-
keepFirstLearningPath: boolean = false
|
|
114
|
+
keepFirstLearningPath: boolean = false,
|
|
109
115
|
) {
|
|
110
116
|
const dateWithTimezone = formatLocalDateTime(userDate)
|
|
111
117
|
const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
|
|
@@ -115,7 +121,7 @@ export async function updateDailySession(
|
|
|
115
121
|
keepFirstLearningPath: keepFirstLearningPath,
|
|
116
122
|
}
|
|
117
123
|
try {
|
|
118
|
-
const response = (await POST(url, body)) as DailySessionResponse|''
|
|
124
|
+
const response = (await POST(url, body)) as DailySessionResponse | ''
|
|
119
125
|
|
|
120
126
|
if (response || response === '') { // refresh cached value
|
|
121
127
|
const urlGet: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${encodeURIComponent(dateWithTimezone)}`
|
|
@@ -173,8 +179,8 @@ export async function startLearningPath(brand: string, learningPathId: number) {
|
|
|
173
179
|
|
|
174
180
|
async function dataPromiseGET(
|
|
175
181
|
url: string,
|
|
176
|
-
forceRefresh: boolean
|
|
177
|
-
): Promise<DailySessionResponse | ActiveLearningPathResponse |
|
|
182
|
+
forceRefresh: boolean,
|
|
183
|
+
): Promise<DailySessionResponse | ActiveLearningPathResponse | ''> {
|
|
178
184
|
if (url.includes('daily-session')) {
|
|
179
185
|
if (!dailySessionPromise || forceRefresh) {
|
|
180
186
|
dailySessionPromise = GET(url, {
|
|
@@ -203,8 +209,8 @@ export async function resetAllLearningPaths() {
|
|
|
203
209
|
await Promise.all([
|
|
204
210
|
db.contentProgress.eraseProgressMany(all.intros, null),
|
|
205
211
|
...all.learning_paths.map((id) =>
|
|
206
|
-
db.contentProgress.eraseProgress(id, {id, type: COLLECTION_TYPE.LEARNING_PATH})
|
|
207
|
-
)
|
|
212
|
+
db.contentProgress.eraseProgress(id, { id, type: COLLECTION_TYPE.LEARNING_PATH }),
|
|
213
|
+
),
|
|
208
214
|
])
|
|
209
215
|
}),
|
|
210
216
|
POST(url, {}),
|
|
@@ -230,15 +236,15 @@ export async function getEnrichedLearningPath(learningPathId) {
|
|
|
230
236
|
addProgressTimestamp: true,
|
|
231
237
|
addResumeTimeSeconds: true,
|
|
232
238
|
addNavigateTo: true,
|
|
233
|
-
}
|
|
239
|
+
},
|
|
234
240
|
)) as any
|
|
235
241
|
// add awards to LP parents only
|
|
236
242
|
response = await addContextToLearningPaths(() => response, { addAwards: true })
|
|
237
243
|
if (!response) return response
|
|
238
244
|
|
|
239
245
|
response.children = mapContentToParent(
|
|
240
|
-
|
|
241
|
-
|
|
246
|
+
response.children,
|
|
247
|
+
{ lessonType: LEARNING_PATH_LESSON, parentContentId: learningPathId },
|
|
242
248
|
)
|
|
243
249
|
return response
|
|
244
250
|
}
|
|
@@ -262,7 +268,7 @@ export async function getEnrichedLearningPaths(learningPathIds: number[]) {
|
|
|
262
268
|
addProgressTimestamp: true,
|
|
263
269
|
addResumeTimeSeconds: true,
|
|
264
270
|
addNavigateTo: true,
|
|
265
|
-
}
|
|
271
|
+
},
|
|
266
272
|
)) as any
|
|
267
273
|
// add awards to LP parents only
|
|
268
274
|
response = await addContextToLearningPaths(() => response, { addAwards: true })
|
|
@@ -271,8 +277,8 @@ export async function getEnrichedLearningPaths(learningPathIds: number[]) {
|
|
|
271
277
|
|
|
272
278
|
response.forEach((learningPath) => {
|
|
273
279
|
learningPath.children = mapContentToParent(
|
|
274
|
-
|
|
275
|
-
|
|
280
|
+
learningPath.children,
|
|
281
|
+
{ lessonType: LEARNING_PATH_LESSON, parentContentId: learningPath.id },
|
|
276
282
|
)
|
|
277
283
|
})
|
|
278
284
|
return response
|
|
@@ -299,8 +305,8 @@ export async function getLearningPathLessonsByIds(contentIds, learningPathId) {
|
|
|
299
305
|
* @param options.parentContentId
|
|
300
306
|
*/
|
|
301
307
|
export function mapContentToParent(
|
|
302
|
-
|
|
303
|
-
options?: { lessonType?: string; parentContentId?: number }
|
|
308
|
+
lessons: any,
|
|
309
|
+
options?: { lessonType?: string; parentContentId?: number },
|
|
304
310
|
) {
|
|
305
311
|
if (!lessons || (Array.isArray(lessons) && lessons.length === 0)) return lessons
|
|
306
312
|
|
|
@@ -363,7 +369,7 @@ interface fetchLearningPathLessonsResponse {
|
|
|
363
369
|
export async function fetchLearningPathLessons(
|
|
364
370
|
learningPathId: number,
|
|
365
371
|
brand: string,
|
|
366
|
-
userDate: Date
|
|
372
|
+
userDate: Date,
|
|
367
373
|
) {
|
|
368
374
|
const learningPath = await getEnrichedLearningPath(learningPathId)
|
|
369
375
|
if (!learningPath || learningPath.children?.length === 0) return null
|
|
@@ -420,7 +426,7 @@ export async function fetchLearningPathLessons(
|
|
|
420
426
|
if (previousContentIds.length !== 0) {
|
|
421
427
|
previousLPDailies = await getLearningPathLessonsByIds(
|
|
422
428
|
previousContentIds,
|
|
423
|
-
previousLearningPathId
|
|
429
|
+
previousLearningPathId,
|
|
424
430
|
)
|
|
425
431
|
}
|
|
426
432
|
if (nextContentIds.length !== 0) {
|
|
@@ -429,7 +435,7 @@ export async function fetchLearningPathLessons(
|
|
|
429
435
|
lessons.map((lesson) => ({
|
|
430
436
|
...lesson,
|
|
431
437
|
in_next_learning_path: learningPath.progressStatus === STATE.COMPLETED,
|
|
432
|
-
}))
|
|
438
|
+
})),
|
|
433
439
|
)
|
|
434
440
|
}
|
|
435
441
|
|
|
@@ -456,7 +462,7 @@ export async function fetchLearningPathLessons(
|
|
|
456
462
|
* @returns {Promise<number[]>} Array with completed content IDs
|
|
457
463
|
*/
|
|
458
464
|
export async function fetchLearningPathProgressCheckLessons(
|
|
459
|
-
contentIds: number[]
|
|
465
|
+
contentIds: number[],
|
|
460
466
|
): Promise<number[]> {
|
|
461
467
|
let query = await getAllCompletedByIds(contentIds)
|
|
462
468
|
let completedProgress = query.data.map((progress) => progress.content_id)
|
|
@@ -467,6 +473,7 @@ interface completeMethodIntroVideo {
|
|
|
467
473
|
intro_video_response: SyncWriteDTO<ContentProgress, any> | null
|
|
468
474
|
active_path_response: ActiveLearningPathResponse
|
|
469
475
|
}
|
|
476
|
+
|
|
470
477
|
/**
|
|
471
478
|
* Handles completion of method intro video and other related actions.
|
|
472
479
|
* @param introVideoId - The method intro video content ID. If not provided, does not `complete` intro video.
|
|
@@ -476,14 +483,14 @@ interface completeMethodIntroVideo {
|
|
|
476
483
|
* @returns {Promise<Object>} response.active_path_response - The set active learning path response.
|
|
477
484
|
*/
|
|
478
485
|
export async function completeMethodIntroVideo(
|
|
479
|
-
introVideoId: number|null,
|
|
480
|
-
brand: string
|
|
486
|
+
introVideoId: number | null,
|
|
487
|
+
brand: string,
|
|
481
488
|
): Promise<completeMethodIntroVideo> {
|
|
482
489
|
let response = {} as completeMethodIntroVideo
|
|
483
490
|
|
|
484
491
|
const [intro_video_response, methodStructure] = await Promise.all([
|
|
485
492
|
introVideoId ? completeIfNotCompleted(introVideoId) : Promise.resolve(null),
|
|
486
|
-
fetchMethodV2Structure(brand)
|
|
493
|
+
fetchMethodV2Structure(brand),
|
|
487
494
|
])
|
|
488
495
|
response.intro_video_response = intro_video_response
|
|
489
496
|
|
|
@@ -492,7 +499,7 @@ export async function completeMethodIntroVideo(
|
|
|
492
499
|
response.active_path_response = await methodIntroVideoCompleteActions(
|
|
493
500
|
brand,
|
|
494
501
|
firstLearningPathId,
|
|
495
|
-
new Date()
|
|
502
|
+
new Date(),
|
|
496
503
|
)
|
|
497
504
|
|
|
498
505
|
return response
|
|
@@ -511,6 +518,7 @@ interface completeLearningPathIntroVideo {
|
|
|
511
518
|
lesson_import_response: SyncWriteDTO<ContentProgress, any> | null
|
|
512
519
|
update_dailies_response: DailySessionResponse | null
|
|
513
520
|
}
|
|
521
|
+
|
|
514
522
|
/**
|
|
515
523
|
* Handles completion of learning path intro video and other related actions.
|
|
516
524
|
* @param introVideoId - The learning path intro video content ID.
|
|
@@ -527,14 +535,14 @@ export async function completeLearningPathIntroVideo(
|
|
|
527
535
|
introVideoId: number,
|
|
528
536
|
learningPathId: number,
|
|
529
537
|
lessonsToImport: number[] | null,
|
|
530
|
-
brand: string
|
|
538
|
+
brand: string,
|
|
531
539
|
) {
|
|
532
540
|
let response = {} as completeLearningPathIntroVideo
|
|
533
541
|
const collection: CollectionObject = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
|
|
534
542
|
|
|
535
543
|
const [anyIntroComplete, activePath] = await Promise.all([
|
|
536
544
|
hasAnyMethodV2IntroCompleted(),
|
|
537
|
-
getActivePath(brand)
|
|
545
|
+
getActivePath(brand),
|
|
538
546
|
])
|
|
539
547
|
|
|
540
548
|
let lateMethodSetup = false
|
|
@@ -561,7 +569,7 @@ export async function completeLearningPathIntroVideo(
|
|
|
561
569
|
}
|
|
562
570
|
|
|
563
571
|
async function completeIfNotCompleted(
|
|
564
|
-
contentId: number
|
|
572
|
+
contentId: number,
|
|
565
573
|
): Promise<SyncWriteDTO<ContentProgress, any> | null> {
|
|
566
574
|
const introVideoStatus = await getProgressState(contentId)
|
|
567
575
|
|
|
@@ -570,20 +578,14 @@ async function completeIfNotCompleted(
|
|
|
570
578
|
|
|
571
579
|
async function resetIfPossible(
|
|
572
580
|
contentId: number,
|
|
573
|
-
collection: CollectionParameter = null
|
|
581
|
+
collection: CollectionParameter = null,
|
|
574
582
|
): Promise<SyncWriteDTO<ContentProgress, any> | null> {
|
|
575
583
|
const status = await getProgressState(contentId, collection)
|
|
576
584
|
|
|
577
585
|
return status !== '' ? await contentStatusReset(contentId, collection) : null
|
|
578
586
|
}
|
|
579
587
|
|
|
580
|
-
export async function
|
|
581
|
-
contentId: number,
|
|
582
|
-
collection: CollectionObject | null
|
|
583
|
-
) {
|
|
584
|
-
if (collection?.type !== COLLECTION_TYPE.LEARNING_PATH) return
|
|
585
|
-
if (contentId !== collection?.id) return
|
|
586
|
-
|
|
588
|
+
export async function onLearningPathCompletedActions(contentId: number) {
|
|
587
589
|
const learningPathId = contentId
|
|
588
590
|
const learningPath = await getEnrichedLearningPath(learningPathId)
|
|
589
591
|
|
|
@@ -628,7 +630,7 @@ export async function mapContentsThatWereLastProgressedFromMethod(objects: any[]
|
|
|
628
630
|
|
|
629
631
|
let filtered = objects.filter((obj) => trueIds.includes(obj.id))
|
|
630
632
|
|
|
631
|
-
filtered = await mapLearningPathParentsTo(filtered, {type: true, parent_id: true})
|
|
633
|
+
filtered = await mapLearningPathParentsTo(filtered, { type: true, parent_id: true })
|
|
632
634
|
|
|
633
635
|
// Map each filtered item back into the total contents object
|
|
634
636
|
objects = objects.map((item) => {
|
|
@@ -639,7 +641,10 @@ export async function mapContentsThatWereLastProgressedFromMethod(objects: any[]
|
|
|
639
641
|
|
|
640
642
|
}
|
|
641
643
|
|
|
642
|
-
export async function mapLearningPathParentsTo(objects: any[], fieldsToMap?: {
|
|
644
|
+
export async function mapLearningPathParentsTo(objects: any[], fieldsToMap?: {
|
|
645
|
+
type?: boolean,
|
|
646
|
+
parent_id?: boolean
|
|
647
|
+
}): Promise<object[]> {
|
|
643
648
|
const ids = objects.map((obj: any) => obj.id) as number[]
|
|
644
649
|
const hierarchy = await fetchParentChildRelationshipsFor(ids, COLLECTION_TYPE.LEARNING_PATH)
|
|
645
650
|
|
|
@@ -654,7 +659,7 @@ export async function mapLearningPathParentsTo(objects: any[], fieldsToMap?: {ty
|
|
|
654
659
|
const parent_id = parentMap.get(obj.id) ?? undefined
|
|
655
660
|
return mapContentToParent(obj, {
|
|
656
661
|
lessonType: fieldsToMap.type ? LEARNING_PATH_LESSON : undefined,
|
|
657
|
-
parentContentId: fieldsToMap.parent_id ? parent_id : undefined
|
|
662
|
+
parentContentId: fieldsToMap.parent_id ? parent_id : undefined,
|
|
658
663
|
})
|
|
659
664
|
})
|
|
660
665
|
}
|