Skip to content

Commit f151869

Browse files
committed
refactor: Extract MigrationStatus Chip from CardList
1 parent 09ac876 commit f151869

12 files changed

Lines changed: 123 additions & 99 deletions

File tree

src/library-authoring/data/api.mocks.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,3 +1133,17 @@ mockGetCourseImports.applyMock = () => jest.spyOn(
11331133
api,
11341134
'getCourseImports',
11351135
).mockImplementation(mockGetCourseImports);
1136+
1137+
export const mockGetMigrationInfo = {
1138+
applyMock: () => jest.spyOn(api, 'getMigrationInfo').mockResolvedValue(
1139+
camelCaseObject({
1140+
'course-v1:HarvardX+123+2023': [{
1141+
sourceKey: 'course-v1:HarvardX+123+2023',
1142+
targetCollectionKey: 'ltc:org:coll-1',
1143+
targetCollectionTitle: 'Collection 1',
1144+
targetKey: mockContentLibrary.libraryId,
1145+
targetTitle: 'Library 1',
1146+
}],
1147+
}),
1148+
),
1149+
};

src/library-authoring/data/api.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,3 +809,24 @@ export async function getCourseImports(libraryId: string): Promise<CourseImport[
809809
const { data } = await getAuthenticatedHttpClient().get(getCourseImportsApiUrl(libraryId));
810810
return camelCaseObject(data);
811811
}
812+
813+
export interface MigrationInfo {
814+
sourceKey: string;
815+
targetCollectionKey: string;
816+
targetCollectionTitle: string;
817+
targetKey: string;
818+
targetTitle: string;
819+
}
820+
821+
/**
822+
* Get the migration info data for a list of source keys
823+
*/
824+
export async function getMigrationInfo(sourceKeys: string[]): Promise<Record<string, MigrationInfo[]>> {
825+
const client = getAuthenticatedHttpClient();
826+
827+
const params = new URLSearchParams();
828+
sourceKeys.forEach(key => params.append('source_keys', key));
829+
830+
const { data } = await client.get(`${getApiBaseUrl()}/api/modulestore_migrator/v1/migration_info/`, { params });
831+
return camelCaseObject(data);
832+
}

src/library-authoring/data/apiHooks.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ export const libraryAuthoringQueryKeys = {
9393
...libraryAuthoringQueryKeys.contentLibrary(libraryId),
9494
'courseImports',
9595
],
96+
migrationInfo: (sourceKeys: string[]) => [
97+
...libraryAuthoringQueryKeys.all,
98+
'migrationInfo',
99+
...sourceKeys,
100+
],
96101
};
97102

98103
export const xblockQueryKeys = {
@@ -965,3 +970,13 @@ export const useCourseImports = (libraryId: string) => (
965970
queryFn: () => api.getCourseImports(libraryId),
966971
})
967972
);
973+
974+
/**
975+
* Returns the migration info of a given source list
976+
*/
977+
export const useMigrationInfo = (sourcesKeys: string[]) => (
978+
useQuery({
979+
queryKey: libraryAuthoringQueryKeys.migrationInfo(sourcesKeys),
980+
queryFn: () => api.getMigrationInfo(sourcesKeys),
981+
})
982+
);

src/library-authoring/import-course/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ const messages = defineMessages({
159159
defaultMessage: 'The selected course is being analyzed for import and review',
160160
description: 'Body of the card in loading state for the import details of a imported course.',
161161
},
162+
previouslyImported: {
163+
id: 'course-authoring.library-authoring.import-course.course-list.card.previously-imported.text',
164+
defaultMessage: 'Previously Imported',
165+
description: 'Chip that indicates that the course has been previously imported.',
166+
},
162167
});
163168

164169
export default messages;

src/library-authoring/import-course/stepper/ImportStepperPage.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import { initialState } from '@src/studio-home/factories/mockApiResponses';
1010
import { RequestStatus } from '@src/data/constants';
1111
import { type DeprecatedReduxState } from '@src/store';
1212
import studioHomeMock from '@src/studio-home/__mocks__/studioHomeMock';
13-
import { mockGetMigrationInfo } from '@src/studio-home/data/api.mocks';
1413
import { getCourseDetailsApiUrl } from '@src/course-outline/data/api';
1514
import { LibraryProvider } from '@src/library-authoring/common/context/LibraryContext';
16-
import { mockContentLibrary } from '@src/library-authoring/data/api.mocks';
15+
import { mockContentLibrary, mockGetMigrationInfo } from '@src/library-authoring/data/api.mocks';
1716
import { ImportStepperPage } from './ImportStepperPage';
1817

1918
let axiosMock;

src/library-authoring/import-course/stepper/ImportStepperPage.tsx

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,67 @@
1-
import { useState } from 'react';
1+
import { useMemo, useState } from 'react';
22
import { Helmet } from 'react-helmet';
33
import { useNavigate } from 'react-router-dom';
44
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
55
import {
6-
ActionRow, Button, Container, Layout, Stepper,
6+
ActionRow, Button, Chip, Container, Layout, Stepper,
77
} from '@openedx/paragon';
88

9-
import { CoursesList } from '@src/studio-home/tabs-section/courses-tab';
9+
import { CoursesList, MigrationStatusProps } from '@src/studio-home/tabs-section/courses-tab';
1010
import { useStudioHome } from '@src/studio-home/hooks';
1111
import { useLibraryContext } from '@src/library-authoring/common/context/LibraryContext';
1212
import Loading from '@src/generic/Loading';
1313

1414
import Header from '@src/header';
1515
import SubHeader from '@src/generic/sub-header/SubHeader';
16+
import { useMigrationInfo } from '@src/library-authoring/data/apiHooks';
1617
import { ReviewImportDetails } from './ReviewImportDetails';
1718
import messages from '../messages';
1819
import { HelpSidebar } from '../HelpSidebar';
1920

2021
type MigrationStep = 'select-course' | 'review-details';
2122

23+
export const MigrationStatus = ({
24+
courseId,
25+
allVisibleCourseIds,
26+
}: MigrationStatusProps) => {
27+
const { libraryId } = useLibraryContext();
28+
29+
const {
30+
data: migrationInfoData,
31+
} = useMigrationInfo(allVisibleCourseIds);
32+
33+
const processedMigrationInfo = useMemo(() => {
34+
const result = {};
35+
if (migrationInfoData) {
36+
for (const libraries of Object.values(migrationInfoData)) {
37+
// The map key in `migrationInfoData` is in camelCase.
38+
// In the processed map, we use the key in its original form.
39+
result[libraries[0].sourceKey] = libraries.map(item => item.targetKey);
40+
}
41+
}
42+
return result;
43+
}, [migrationInfoData]);
44+
45+
const isPreviouslyMigrated = (
46+
courseId in processedMigrationInfo && processedMigrationInfo[courseId].includes(libraryId)
47+
);
48+
49+
if (!isPreviouslyMigrated) {
50+
return null;
51+
}
52+
53+
return (
54+
<div
55+
key={`${courseId}-${processedMigrationInfo[courseId].join('-')}`}
56+
className="previously-migrated-chip"
57+
>
58+
<Chip>
59+
<FormattedMessage {...messages.previouslyImported} />
60+
</Chip>
61+
</div>
62+
);
63+
};
64+
2265
export const ImportStepperPage = () => {
2366
const intl = useIntl();
2467
const navigate = useNavigate();
@@ -69,7 +112,7 @@ export const ImportStepperPage = () => {
69112
<CoursesList
70113
selectedCourseId={selectedCourseId}
71114
handleSelect={setSelectedCourseId}
72-
currentLibraryId={libraryId}
115+
cardMigrationStatusWidget={MigrationStatus}
73116
/>
74117
</Stepper.Step>
75118
<Stepper.Step

src/studio-home/data/api.mocks.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { camelCaseObject } from '@edx/frontend-platform';
22

33
import { createAxiosError } from '@src/testUtils';
44
import * as api from './api';
5-
import { generateGetStudioHomeLibrariesApiResponse, generateGetMigrationInfo } from '../factories/mockApiResponses';
5+
import { generateGetStudioHomeLibrariesApiResponse } from '../factories/mockApiResponses';
66

77
/**
88
* Mock for `getContentLibraryV2List()`
@@ -21,9 +21,3 @@ export const mockGetStudioHomeLibraries = {
2121
libraries: [],
2222
}),
2323
};
24-
25-
export const mockGetMigrationInfo = {
26-
applyMock: () => jest.spyOn(api, 'getMigrationInfo').mockResolvedValue(
27-
camelCaseObject(generateGetMigrationInfo()),
28-
),
29-
};

src/studio-home/data/api.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,6 @@ export interface LibrariesV1ListData {
4949
libraries: LibraryV1Data[];
5050
}
5151

52-
export interface MigrationInfo {
53-
sourceKey: string;
54-
targetCollectionKey: string;
55-
targetCollectionTitle: string;
56-
targetKey: string;
57-
targetTitle: string;
58-
}
59-
6052
export async function getStudioHomeLibraries(): Promise<LibrariesV1ListData> {
6153
const { data } = await getAuthenticatedHttpClient().get(`${getStudioHomeApiUrl()}/libraries`);
6254
return camelCaseObject(data);
@@ -77,16 +69,3 @@ export async function sendRequestForCourseCreator(): Promise<object> {
7769
const { data } = await getAuthenticatedHttpClient().post(getRequestCourseCreatorUrl());
7870
return camelCaseObject(data);
7971
}
80-
81-
/**
82-
* Get the migration info data for a list of source keys
83-
*/
84-
export async function getMigrationInfo(sourceKeys: string[]): Promise<Record<string, MigrationInfo[]>> {
85-
const client = getAuthenticatedHttpClient();
86-
87-
const params = new URLSearchParams();
88-
sourceKeys.forEach(key => params.append('source_keys', key));
89-
90-
const { data } = await client.get(`${getApiBaseUrl()}/api/modulestore_migrator/v1/migration_info/`, { params });
91-
return camelCaseObject(data);
92-
}

src/studio-home/data/apiHooks.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { skipToken, useQuery } from '@tanstack/react-query';
2-
import { getStudioHomeLibraries, getMigrationInfo } from './api';
1+
import { useQuery } from '@tanstack/react-query';
2+
import { getStudioHomeLibraries } from './api';
33

44
export const studioHomeQueryKeys = {
55
all: ['studioHome'],
66
/**
77
* Base key for list of v1/legacy libraries
88
*/
99
librariesV1: () => [...studioHomeQueryKeys.all, 'librariesV1'],
10-
migrationInfo: (sourceKeys: string[]) => [...studioHomeQueryKeys.all, 'migrationInfo', ...sourceKeys],
1110
};
1211

1312
export const useLibrariesV1Data = (enabled: boolean = true) => (
@@ -17,10 +16,3 @@ export const useLibrariesV1Data = (enabled: boolean = true) => (
1716
enabled,
1817
})
1918
);
20-
21-
export const useMigrationInfo = (sourcesKeys: string[], enabled: boolean = true) => (
22-
useQuery({
23-
queryKey: studioHomeQueryKeys.migrationInfo(sourcesKeys),
24-
queryFn: enabled ? () => getMigrationInfo(sourcesKeys) : skipToken,
25-
})
26-
);

src/studio-home/factories/mockApiResponses.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { type DeprecatedReduxState } from '@src/store';
22
import { RequestStatus } from '@src/data/constants';
3-
import { mockContentLibrary } from '@src/library-authoring/data/api.mocks';
43

54
export const courseId = 'course';
65

@@ -167,13 +166,3 @@ export const generateNewVideoApiResponse = () => ({
167166
upload_url: 'http://testing.org',
168167
}],
169168
});
170-
171-
export const generateGetMigrationInfo = () => ({
172-
'course-v1:HarvardX+123+2023': [{
173-
sourceKey: 'course-v1:HarvardX+123+2023',
174-
targetCollectionKey: 'ltc:org:coll-1',
175-
targetCollectionTitle: 'Collection 1',
176-
targetKey: mockContentLibrary.libraryId,
177-
targetTitle: 'Library 1',
178-
}],
179-
});

0 commit comments

Comments
 (0)