feat: import analysis step (#2657)
Shows course analysis information in review import details step in course import stepper page. Also handles alerts based on the import status, like, reimport or unsupported number of blocks.
This commit is contained in:
135
src/data/api.mocks.ts
Normal file
135
src/data/api.mocks.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import * as api from './api';
|
||||
|
||||
export async function mockGetMigrationStatus(migrationId: string): Promise<api.MigrateTaskStatusData> {
|
||||
switch (migrationId) {
|
||||
case mockGetMigrationStatus.migrationId:
|
||||
return mockGetMigrationStatus.migrationStatusData;
|
||||
case mockGetMigrationStatus.migrationIdFailed:
|
||||
return mockGetMigrationStatus.migrationStatusFailedData;
|
||||
case mockGetMigrationStatus.migrationIdMultiple:
|
||||
return mockGetMigrationStatus.migrationStatusFailedMultipleData;
|
||||
case mockGetMigrationStatus.migrationIdOneLibrary:
|
||||
return mockGetMigrationStatus.migrationStatusFailedOneLibraryData;
|
||||
default:
|
||||
/* istanbul ignore next */
|
||||
throw new Error(`mockGetMigrationStatus: unknown migration ID "${migrationId}"`);
|
||||
}
|
||||
}
|
||||
|
||||
mockGetMigrationStatus.migrationId = '1';
|
||||
mockGetMigrationStatus.migrationStatusData = {
|
||||
uuid: mockGetMigrationStatus.migrationId,
|
||||
state: 'Succeeded',
|
||||
stateText: 'Succeeded',
|
||||
completedSteps: 9,
|
||||
totalSteps: 9,
|
||||
attempts: 1,
|
||||
created: '',
|
||||
modified: '',
|
||||
artifacts: [],
|
||||
parameters: [
|
||||
{
|
||||
source: 'legacy-lib-1',
|
||||
target: 'lib',
|
||||
compositionLevel: 'component',
|
||||
repeatHandlingStrategy: 'update',
|
||||
preserveUrlSlugs: false,
|
||||
targetCollectionSlug: 'coll-1',
|
||||
forwardSourceToTarget: true,
|
||||
isFailed: false,
|
||||
},
|
||||
],
|
||||
} as api.MigrateTaskStatusData;
|
||||
mockGetMigrationStatus.migrationIdFailed = '2';
|
||||
mockGetMigrationStatus.migrationStatusFailedData = {
|
||||
uuid: mockGetMigrationStatus.migrationId,
|
||||
state: 'Failed',
|
||||
stateText: 'Failed',
|
||||
completedSteps: 9,
|
||||
totalSteps: 9,
|
||||
attempts: 1,
|
||||
created: '',
|
||||
modified: '',
|
||||
artifacts: [],
|
||||
parameters: [
|
||||
{
|
||||
source: 'legacy-lib-1',
|
||||
target: 'lib',
|
||||
compositionLevel: 'component',
|
||||
repeatHandlingStrategy: 'update',
|
||||
preserveUrlSlugs: false,
|
||||
targetCollectionSlug: 'coll-1',
|
||||
forwardSourceToTarget: true,
|
||||
isFailed: true,
|
||||
},
|
||||
],
|
||||
} as api.MigrateTaskStatusData;
|
||||
mockGetMigrationStatus.migrationIdMultiple = '3';
|
||||
mockGetMigrationStatus.migrationStatusFailedMultipleData = {
|
||||
uuid: mockGetMigrationStatus.migrationId,
|
||||
state: 'Succeeded',
|
||||
stateText: 'Succeeded',
|
||||
completedSteps: 9,
|
||||
totalSteps: 9,
|
||||
attempts: 1,
|
||||
created: '',
|
||||
modified: '',
|
||||
artifacts: [],
|
||||
parameters: [
|
||||
{
|
||||
source: 'legacy-lib-1',
|
||||
target: 'lib',
|
||||
compositionLevel: 'component',
|
||||
repeatHandlingStrategy: 'update',
|
||||
preserveUrlSlugs: false,
|
||||
targetCollectionSlug: 'coll-1',
|
||||
forwardSourceToTarget: true,
|
||||
isFailed: true,
|
||||
},
|
||||
{
|
||||
source: 'legacy-lib-2',
|
||||
target: 'lib',
|
||||
compositionLevel: 'component',
|
||||
repeatHandlingStrategy: 'update',
|
||||
preserveUrlSlugs: false,
|
||||
targetCollectionSlug: 'coll-1',
|
||||
forwardSourceToTarget: true,
|
||||
isFailed: true,
|
||||
},
|
||||
],
|
||||
} as api.MigrateTaskStatusData;
|
||||
mockGetMigrationStatus.migrationIdOneLibrary = '4';
|
||||
mockGetMigrationStatus.migrationStatusFailedOneLibraryData = {
|
||||
uuid: mockGetMigrationStatus.migrationId,
|
||||
state: 'Succeeded',
|
||||
stateText: 'Succeeded',
|
||||
completedSteps: 9,
|
||||
totalSteps: 9,
|
||||
attempts: 1,
|
||||
created: '',
|
||||
modified: '',
|
||||
artifacts: [],
|
||||
parameters: [
|
||||
{
|
||||
source: 'legacy-lib-1',
|
||||
target: 'lib',
|
||||
compositionLevel: 'component',
|
||||
repeatHandlingStrategy: 'update',
|
||||
preserveUrlSlugs: false,
|
||||
targetCollectionSlug: 'coll-1',
|
||||
forwardSourceToTarget: true,
|
||||
isFailed: true,
|
||||
},
|
||||
{
|
||||
source: 'legacy-lib-2',
|
||||
target: 'lib',
|
||||
compositionLevel: 'component',
|
||||
repeatHandlingStrategy: 'update',
|
||||
preserveUrlSlugs: false,
|
||||
targetCollectionSlug: 'coll-1',
|
||||
forwardSourceToTarget: true,
|
||||
isFailed: false,
|
||||
},
|
||||
],
|
||||
} as api.MigrateTaskStatusData;
|
||||
mockGetMigrationStatus.applyMock = () => jest.spyOn(api, 'getModulestoreMigrationStatus').mockImplementation(mockGetMigrationStatus);
|
||||
34
src/data/api.test.ts
Normal file
34
src/data/api.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { initializeMocks } from '../testUtils';
|
||||
import * as api from './api';
|
||||
|
||||
let axiosMock;
|
||||
|
||||
describe('legacy libraries migration API', () => {
|
||||
beforeEach(() => {
|
||||
({ axiosMock } = initializeMocks());
|
||||
});
|
||||
|
||||
describe('getModulestoreMigrationStatus', () => {
|
||||
it('should get migration status', async () => {
|
||||
const migrationId = '1';
|
||||
const url = api.getModulestoreMigrationStatusUrl(migrationId);
|
||||
axiosMock.onGet(url).reply(200);
|
||||
await api.getModulestoreMigrationStatus(migrationId);
|
||||
|
||||
expect(axiosMock.history.get[0].url).toEqual(url);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkMigrateLegacyLibraries', () => {
|
||||
it('should call bulk migrate legacy libraries', async () => {
|
||||
const url = api.bulkModulestoreMigrateUrl();
|
||||
axiosMock.onPost(url).reply(200);
|
||||
await api.bulkModulestoreMigrate({
|
||||
sources: [],
|
||||
target: '1',
|
||||
});
|
||||
|
||||
expect(axiosMock.history.post[0].url).toEqual(url);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,18 @@
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { camelCaseObject, getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
const getStudioBaseUrl = () => getConfig().STUDIO_BASE_URL as string;
|
||||
|
||||
/**
|
||||
* Get the URL to check the migration task status
|
||||
*/
|
||||
export const getModulestoreMigrationStatusUrl = (migrationId: string) => `${getStudioBaseUrl()}/api/modulestore_migrator/v1/migrations/${migrationId}/`;
|
||||
|
||||
/**
|
||||
* Get the URL for bulk migrate content to libraries
|
||||
*/
|
||||
export const bulkModulestoreMigrateUrl = () => `${getStudioBaseUrl()}/api/modulestore_migrator/v1/bulk_migration/`;
|
||||
|
||||
export const getApiWaffleFlagsUrl = (courseId?: string): string => {
|
||||
const baseUrl = getStudioBaseUrl();
|
||||
const apiPath = '/api/contentstore/v1/course_waffle_flags';
|
||||
@@ -72,3 +82,60 @@ export async function getWaffleFlags(courseId?: string): Promise<WaffleFlagsStat
|
||||
.get(getApiWaffleFlagsUrl(courseId));
|
||||
return normalizeCourseDetail(data);
|
||||
}
|
||||
|
||||
export interface MigrateArtifacts {
|
||||
source: string;
|
||||
target: string;
|
||||
compositionLevel: string;
|
||||
repeatHandlingStrategy: 'update' | 'skip' | 'fork';
|
||||
preserveUrlSlugs: boolean;
|
||||
targetCollectionSlug: string;
|
||||
forwardSourceToTarget: boolean;
|
||||
isFailed: boolean;
|
||||
}
|
||||
|
||||
export interface MigrateTaskStatusData {
|
||||
state: string;
|
||||
stateText: string;
|
||||
completedSteps: number;
|
||||
totalSteps: number;
|
||||
attempts: number;
|
||||
created: string;
|
||||
modified: string;
|
||||
artifacts: string[];
|
||||
uuid: string;
|
||||
parameters: MigrateArtifacts[];
|
||||
}
|
||||
|
||||
export interface BulkMigrateRequestData {
|
||||
sources: string[];
|
||||
target: string;
|
||||
targetCollectionSlugList?: string[];
|
||||
createCollections?: boolean;
|
||||
compositionLevel?: string;
|
||||
repeatHandlingStrategy?: string;
|
||||
preserveUrlSlugs?: boolean;
|
||||
forwardSourceToTarget?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get migration task status
|
||||
*/
|
||||
export async function getModulestoreMigrationStatus(
|
||||
migrationId: string,
|
||||
): Promise<MigrateTaskStatusData> {
|
||||
const client = getAuthenticatedHttpClient();
|
||||
const { data } = await client.get(getModulestoreMigrationStatusUrl(migrationId));
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk migrate content to libraries
|
||||
*/
|
||||
export async function bulkModulestoreMigrate(
|
||||
requestData: BulkMigrateRequestData,
|
||||
): Promise<MigrateTaskStatusData> {
|
||||
const client = getAuthenticatedHttpClient();
|
||||
const { data } = await client.post(bulkModulestoreMigrateUrl(), snakeCaseObject(requestData));
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { getWaffleFlags, waffleFlagDefaults } from './api';
|
||||
import {
|
||||
skipToken, useMutation, useQuery, useQueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { libraryAuthoringQueryKeys } from '@src/library-authoring/data/apiHooks';
|
||||
import {
|
||||
getWaffleFlags,
|
||||
waffleFlagDefaults,
|
||||
bulkModulestoreMigrate,
|
||||
getModulestoreMigrationStatus,
|
||||
BulkMigrateRequestData,
|
||||
} from './api';
|
||||
|
||||
export const migrationQueryKeys = {
|
||||
all: ['contentLibrary'],
|
||||
/**
|
||||
* Base key for data specific to a migration task
|
||||
*/
|
||||
migrationTask: (migrationId?: string | null) => [...migrationQueryKeys.all, migrationId],
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the waffle flags (which enable/disable specific features). They may
|
||||
@@ -30,3 +47,28 @@ export const useWaffleFlags = (courseId?: string) => {
|
||||
isError,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this mutation to migrate multiple sources to a library
|
||||
*/
|
||||
export const useBulkModulestoreMigrate = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (requestData: BulkMigrateRequestData) => bulkModulestoreMigrate(requestData),
|
||||
onSettled: (_data, _err, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.courseImports(variables.target) });
|
||||
queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.allMigrationInfo() });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the migration status
|
||||
*/
|
||||
export const useModulestoreMigrationStatus = (migrationId: string | null) => (
|
||||
useQuery({
|
||||
queryKey: migrationQueryKeys.migrationTask(migrationId),
|
||||
queryFn: migrationId ? () => getModulestoreMigrationStatus(migrationId!) : skipToken,
|
||||
refetchInterval: 1000, // Refresh every second
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user