Skip to content

Commit 68edfa1

Browse files
committed
feat: Add previously migration chip
1 parent a72d417 commit 68edfa1

9 files changed

Lines changed: 106 additions & 8 deletions

File tree

src/library-authoring/LibraryAuthoringPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,11 @@ const LibraryAuthoringPage = ({
406406
<LibrarySidebar />
407407
</div>
408408
)}
409-
<ImportStepperModal isOpen={importModalIsOpen} onClose={closeImportModal} />
409+
<ImportStepperModal
410+
isOpen={importModalIsOpen}
411+
onClose={closeImportModal}
412+
libraryKey={libraryId}
413+
/>
410414
</div>
411415
);
412416
};

src/library-authoring/course-import/ImportStepperModal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import messages from './messages';
1111
type MigrationStep = 'select-course' | 'review-details';
1212

1313
export const ImportStepperModal = ({
14+
libraryKey,
1415
isOpen,
1516
onClose,
1617
}: {
18+
libraryKey: string,
1719
isOpen: boolean,
1820
onClose: () => void,
1921
}) => {
@@ -46,6 +48,7 @@ export const ImportStepperModal = ({
4648
<CoursesList
4749
selectedCourseId={selectedCourseId}
4850
handleSelect={setSelectedCourseId}
51+
currentLibraryId={libraryKey}
4952
/>
5053
</Stepper.Step>
5154
<Stepper.Step

src/studio-home/card-item/index.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import React, { useCallback, useEffect, useRef } from 'react';
1+
import React, {
2+
ReactElement, useCallback, useEffect, useRef,
3+
} from 'react';
24
import { useSelector } from 'react-redux';
35
import {
46
Card,
@@ -208,6 +210,7 @@ interface BaseProps {
208210
migratedToKey?: string;
209211
migratedToTitle?: string;
210212
migratedToCollectionKey?: string | null;
213+
subtitleBeforeComponent?: ReactElement | null;
211214
selectMode?: 'single' | 'multiple';
212215
selectPosition?: 'card' | 'title';
213216
isSelected?: boolean;
@@ -248,6 +251,7 @@ const CardItem: React.FC<Props> = ({
248251
migratedToKey,
249252
migratedToTitle,
250253
migratedToCollectionKey,
254+
subtitleBeforeComponent,
251255
scrollIntoView = false,
252256
}) => {
253257
const intl = useIntl();
@@ -282,6 +286,14 @@ const CardItem: React.FC<Props> = ({
282286
/>
283287
);
284288
}
289+
if (subtitleBeforeComponent) {
290+
subtitle = (
291+
<Stack direction="horizontal" gap={2}>
292+
{subtitleBeforeComponent}
293+
{subtitle}
294+
</Stack>
295+
);
296+
}
285297
return subtitle;
286298
}, [isLibraries, org, number, run, migratedToKey, isMigrated]);
287299

src/studio-home/data/api.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// @ts-check
21
import { camelCaseObject, snakeCaseObject, getConfig } from '@edx/frontend-platform';
32
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
43

@@ -50,6 +49,14 @@ export interface LibrariesV1ListData {
5049
libraries: LibraryV1Data[];
5150
}
5251

52+
export interface MigrationInfo {
53+
sourceKey: string;
54+
targetCollectionKey: string;
55+
targetCollectionTitle: string;
56+
targetKey: string;
57+
targetTitle: string;
58+
}
59+
5360
export async function getStudioHomeLibraries(): Promise<LibrariesV1ListData> {
5461
const { data } = await getAuthenticatedHttpClient().get(`${getStudioHomeApiUrl()}/libraries`);
5562
return camelCaseObject(data);
@@ -70,3 +77,16 @@ export async function sendRequestForCourseCreator(): Promise<object> {
7077
const { data } = await getAuthenticatedHttpClient().post(getRequestCourseCreatorUrl());
7178
return camelCaseObject(data);
7279
}
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: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { useQuery } from '@tanstack/react-query';
2-
import { getStudioHomeLibraries } from './api';
2+
import { getStudioHomeLibraries, getMigrationInfo } 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],
1011
};
1112

1213
export const useLibrariesV1Data = (enabled: boolean = true) => (
@@ -16,3 +17,11 @@ export const useLibrariesV1Data = (enabled: boolean = true) => (
1617
enabled,
1718
})
1819
);
20+
21+
export const useMigrationInfo = (sourcesKeys: string[], enabled: boolean = true) => (
22+
useQuery({
23+
queryKey: studioHomeQueryKeys.migrationInfo(sourcesKeys),
24+
queryFn: () => getMigrationInfo(sourcesKeys),
25+
enabled,
26+
})
27+
);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
.courses-tab-container {
22
min-height: 80vh;
3+
4+
.previously-migrated-chip {
5+
.pgn__chip {
6+
border: 0;
7+
background-color: var(--pgn-color-warning-500);
8+
}
9+
}
310
}

src/studio-home/tabs-section/courses-tab/index.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe('<CoursesTab />', () => {
109109
expect(alertCoursesNotFound).toBeInTheDocument();
110110
});
111111

112-
it('should render processing courses component when isEnabledPagination is false and isShowProcessing is true', () => {
112+
it('should render processing courses component when isEnabledPagination is false and isShowProcessing is true', async () => {
113113
const props = { isShowProcessing: true, isEnabledPagination: false };
114114
const customStoreData = {
115115
studioHomeData: {
@@ -121,7 +121,7 @@ describe('<CoursesTab />', () => {
121121
},
122122
};
123123
renderComponent(props, customStoreData);
124-
const alertCoursesNotFound = screen.queryByTestId('processing-courses-title');
124+
const alertCoursesNotFound = await screen.findByTestId('processing-courses-title');
125125
expect(alertCoursesNotFound).toBeInTheDocument();
126126
});
127127

src/studio-home/tabs-section/courses-tab/index.tsx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useCallback, useMemo } from 'react';
22
import { useLocation } from 'react-router-dom';
33
import { useDispatch, useSelector } from 'react-redux';
44
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
@@ -9,6 +9,7 @@ import {
99
Alert,
1010
Button,
1111
Form,
12+
Chip,
1213
} from '@openedx/paragon';
1314
import { Error } from '@openedx/paragon/icons';
1415

@@ -22,6 +23,7 @@ import ProcessingCourses from '@src/studio-home/processing-courses';
2223
import { LoadingSpinner } from '@src/generic/Loading';
2324
import AlertMessage from '@src/generic/alert-message';
2425
import { RequestStatus } from '@src/data/constants';
26+
import { useMigrationInfo } from '@src/studio-home/data/apiHooks';
2527
import messages from '../messages';
2628
import CoursesFilters from './courses-filters';
2729
import ContactAdministrator from './contact-administrator';
@@ -39,6 +41,7 @@ interface CardListProps {
3941
onClickNewCourse?: () => void;
4042
inSelectMode?: boolean;
4143
selectedCourseId?: string;
44+
currentLibraryId?: string;
4245
}
4346

4447
const CardList = ({
@@ -53,16 +56,37 @@ const CardList = ({
5356
onClickNewCourse = () => {},
5457
inSelectMode = false,
5558
selectedCourseId,
59+
currentLibraryId,
5660
}: CardListProps) => {
5761
const {
5862
courses,
5963
numPages,
6064
optimizationEnabled,
6165
} = useSelector(getStudioHomeData);
6266

67+
const {
68+
data: migrationInfoData,
69+
} = useMigrationInfo(courses?.map(item => item.courseKey) || [], true);
70+
71+
const processedMigrationInfo = useMemo(() => {
72+
const result = {};
73+
if (migrationInfoData) {
74+
for (const libraries of Object.values(migrationInfoData)) {
75+
// The map key in `migrationInfoData` is in camelCase.
76+
// In the processed map, we use the key in its original form.
77+
result[libraries[0].sourceKey] = libraries.map(item => item.targetKey);
78+
}
79+
}
80+
return result;
81+
}, [migrationInfoData]);
82+
6383
const isNotFilteringCourses = !isFiltered && !isLoading;
6484
const hasCourses = courses?.length > 0;
6585

86+
const isPreviouslyMigrated = useCallback((courseKey: string) => (
87+
courseKey in processedMigrationInfo && processedMigrationInfo[courseKey].includes(currentLibraryId)
88+
), [processedMigrationInfo]);
89+
6690
return (
6791
<>
6892
{hasCourses ? (
@@ -79,7 +103,8 @@ const CardList = ({
79103
url,
80104
}) => (
81105
<CardItem
82-
key={courseKey}
106+
// Add `-migrated` to force re-render of the Chip
107+
key={`${courseKey}${isPreviouslyMigrated(courseKey) ? '-migrated' : ''}`}
83108
courseKey={courseKey}
84109
onClick={() => onClickCard?.(courseKey)}
85110
itemId={courseKey}
@@ -93,6 +118,16 @@ const CardList = ({
93118
selectMode={inSelectMode ? 'single' : undefined}
94119
selectPosition={inSelectMode ? 'card' : undefined}
95120
isSelected={inSelectMode && selectedCourseId === courseKey}
121+
subtitleBeforeComponent={isPreviouslyMigrated(courseKey) && (
122+
<div
123+
key={`${courseKey}-${processedMigrationInfo[courseKey].join('-')}`}
124+
className="previously-migrated-chip"
125+
>
126+
<Chip>
127+
<FormattedMessage {...messages.previouslyImported} />
128+
</Chip>
129+
</div>
130+
)}
96131
/>
97132
),
98133
)}
@@ -139,6 +174,7 @@ interface Props {
139174
isShowProcessing?: boolean;
140175
selectedCourseId?: string;
141176
handleSelect?: (courseId: string) => void;
177+
currentLibraryId?: string;
142178
}
143179

144180
export const CoursesList: React.FC<Props> = ({
@@ -147,6 +183,7 @@ export const CoursesList: React.FC<Props> = ({
147183
isShowProcessing = false,
148184
selectedCourseId,
149185
handleSelect,
186+
currentLibraryId,
150187
}) => {
151188
const dispatch = useDispatch();
152189
const intl = useIntl();
@@ -243,6 +280,7 @@ export const CoursesList: React.FC<Props> = ({
243280
isFiltered={isFiltered || false}
244281
inSelectMode
245282
selectedCourseId={selectedCourseId}
283+
currentLibraryId={currentLibraryId}
246284
/>
247285
</Form.RadioSet>
248286
) : (

src/studio-home/tabs-section/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ const messages = defineMessages({
136136
defaultMessage: 'Select All',
137137
description: 'Button to select all libraries when migrate legacy libraries.',
138138
},
139+
previouslyImported: {
140+
id: 'studio-home.course-list.card.previously-imported.text',
141+
defaultMessage: 'Previously Imported',
142+
description: 'Chip that indicates that the course has been previously imported.',
143+
},
139144
});
140145

141146
export default messages;

0 commit comments

Comments
 (0)