diff --git a/src/commands/export.ts b/src/commands/export.ts index 79727b2c..5ffbb543 100644 --- a/src/commands/export.ts +++ b/src/commands/export.ts @@ -17,7 +17,7 @@ import { previewUrlsExportEntity } from "./importExportEntities/entities/preview import { rolesExportEntity } from "./importExportEntities/entities/roles.js"; import { spacesExportEntity } from "./importExportEntities/entities/spaces.js"; import { taxonomiesEntity } from "./importExportEntities/entities/taxonomies.js"; -import { workflowsExportEntity } from "./importExportEntities/entities/workflows.js"; +import { workflowsEntity } from "./importExportEntities/entities/workflows.js"; import { EntityExportDefinition } from "./importExportEntities/entityDefinition.js"; const zip = new JSZip(); @@ -56,7 +56,7 @@ const entityDefinitions: ReadonlyArray> = [ languagesEntity, previewUrlsExportEntity, rolesExportEntity, - workflowsExportEntity, + workflowsEntity, contentTypesSnippetsEntity, contentTypesEntity, contentItemsExportEntity, diff --git a/src/commands/import.ts b/src/commands/import.ts index 03faf4dd..c641d988 100644 --- a/src/commands/import.ts +++ b/src/commands/import.ts @@ -18,6 +18,7 @@ import { } from "./importExportEntities/entities/contentTypesSnippets.js"; import { languagesEntity } from "./importExportEntities/entities/languages.js"; import { taxonomiesEntity } from "./importExportEntities/entities/taxonomies.js"; +import { workflowsEntity } from "./importExportEntities/entities/workflows.js"; import { EntityImportDefinition, ImportContext } from "./importExportEntities/entityDefinition.js"; export const register: RegisterCommand = yargs => @@ -57,6 +58,7 @@ const entityDefinitions: ReadonlyArray> = [ contentItemsExportEntity, updateItemAndTypeReferencesInSnippetsImportEntity, updateItemAndTypeReferencesInTypesImportEntity, + workflowsEntity, ]; type ImportEntitiesParams = Readonly<{ @@ -106,4 +108,6 @@ const createInitialContext = (): ImportContext => ({ contentTypeSnippetIdsWithElementsByOldIds: new Map(), contentItemIdsByOldIds: new Map(), contentTypeIdsWithElementsByOldIds: new Map(), + workflowIdsByOldIds: new Map(), + worfklowStepsIdsByOldIds: new Map(), }); diff --git a/src/commands/importExportEntities/entities/languages.ts b/src/commands/importExportEntities/entities/languages.ts index 861188d5..87d10765 100644 --- a/src/commands/importExportEntities/entities/languages.ts +++ b/src/commands/importExportEntities/entities/languages.ts @@ -1,10 +1,11 @@ import { LanguageContracts, LanguageModels, ManagementClient } from "@kontent-ai/management-sdk"; +import { emptyId } from "../../../constants/ids.js"; import { serially } from "../../../utils/requests.js"; import { notNull } from "../../../utils/typeguards.js"; import { EntityDefinition } from "../entityDefinition.js"; -const defaultLanguageId = "00000000-0000-0000-0000-000000000000"; +const defaultLanguageId = emptyId; export const languagesEntity: EntityDefinition> = { name: "languages", diff --git a/src/commands/importExportEntities/entities/workflows.ts b/src/commands/importExportEntities/entities/workflows.ts index 13338aea..c985fa5e 100644 --- a/src/commands/importExportEntities/entities/workflows.ts +++ b/src/commands/importExportEntities/entities/workflows.ts @@ -1,15 +1,119 @@ -import { WorkflowContracts } from "@kontent-ai/management-sdk"; +import { ManagementClient, WorkflowContracts, WorkflowModels } from "@kontent-ai/management-sdk"; -import { EntityDefinition } from "../entityDefinition.js"; +import { emptyId } from "../../../constants/ids.js"; +import { zip } from "../../../utils/array.js"; +import { serially } from "../../../utils/requests.js"; +import { EntityDefinition, ImportContext } from "../entityDefinition.js"; -export const workflowsExportEntity: EntityDefinition = { +const defaultWorkflowId = emptyId; + +export const workflowsEntity: EntityDefinition = { name: "workflows", fetchEntities: client => client.listWorkflows().toPromise().then(res => res.rawData), serializeEntities: collections => JSON.stringify(collections), - importEntities: () => { - throw new Error("Not supported yet."); + deserializeEntities: JSON.parse, + importEntities: async (client, importWfs, context) => { + const oldProjectDefaultWf = await client.listWorkflows().toPromise() + .then(res => res.data.find(w => w.id === defaultWorkflowId)); + const importDefaultWf = importWfs.find(w => w.id === defaultWorkflowId); + + if (!importDefaultWf || !oldProjectDefaultWf) { + throw new Error(`The default workflow is missing in the imported file or the project to import into.`); + } + + const projectDefaultWf = await updateWorkflow(client, oldProjectDefaultWf, importDefaultWf, context); + const newProjectWfs = await addWorkflows(client, importWfs, context); + + const newDefaultWfStepIdEntries = zip(extractAllStepIds(importDefaultWf), extractAllStepIds(projectDefaultWf)); + + return { + ...context, + workflowIdsByOldIds: new Map([...newProjectWfs.workflows, [defaultWorkflowId, defaultWorkflowId]]), + worfklowStepsIdsByOldIds: new Map([...newProjectWfs.workflowSteps, ...newDefaultWfStepIdEntries]), + }; + }, +}; + +const createWorkflowData = (importWorkflow: WorkflowContracts.IWorkflowContract, context: ImportContext) => ({ + ...importWorkflow, + scopes: importWorkflow.scopes.map(scope => ({ + content_types: scope.content_types + .map(type => ({ id: context.contentTypeIdsWithElementsByOldIds.get(type.id ?? "")?.selfId })), + collections: scope.collections.map(collection => ({ id: context.collectionIdsByOldIds.get(collection.id ?? "") })), + })), + steps: importWorkflow.steps.map(step => ({ + ...step, + role_ids: [], + transitions_to: step.transitions_to.map(transition => { + const transitionWorkflow = extractAllSteps(importWorkflow).find(s => s.id === transition.step.id); + + if (!transitionWorkflow) { + throw new Error(`Could not find worklow step with id ${transition.step.id}. This should never happen.`); + } + + return { step: { codename: transitionWorkflow.codename } }; + }), + })), + published_step: { + ...importWorkflow, + unpublish_role_ids: [], + create_new_version_role_ids: [], }, - deserializeEntities: () => { - throw new Error("Not supported yet."); + archived_step: { + ...importWorkflow.archived_step, + role_ids: [], }, +}); + +const updateWorkflow = async ( + client: ManagementClient, + projectWorkflow: WorkflowModels.Workflow, + importWorkflow: WorkflowContracts.IWorkflowContract, + context: ImportContext, +) => + client + .updateWorkflow() + .byWorkflowId(projectWorkflow.id) + .withData(createWorkflowData(importWorkflow, context)) + .toPromise() + .then(res => res.rawData); + +const addWorkflows = async ( + client: ManagementClient, + importWorkflows: WorkflowContracts.IListWorkflowsResponseContract, + context: ImportContext, +) => { + const responses = await serially( + importWorkflows + .filter(w => w.id !== defaultWorkflowId) + .map(importWorkflow => async () => { + const response = await client + .addWorkflow() + .withData(createWorkflowData(importWorkflow, context)) + .toPromise() + .then(res => res.rawData); + + const stepsIds = zip(extractAllStepIds(importWorkflow), extractAllStepIds(response)); + + return { workflow: [importWorkflow.id, response.id] as const, workflowSteps: stepsIds }; + }), + ); + + return responses + .reduce((prev, current) => ({ + workflows: prev.workflows.concat([current.workflow]), + workflowSteps: prev.workflowSteps.concat(current.workflowSteps), + }), { workflows: [], workflowSteps: [] }); }; + +type ContextWorkflowEntries = Readonly<{ + workflows: ReadonlyArray; + workflowSteps: ReadonlyArray; +}>; + +type AnyStep = Readonly<{ id: string; name: string; codename: string }>; +const extractAllSteps = (wf: WorkflowContracts.IWorkflowContract): ReadonlyArray => + (wf.steps as AnyStep[]).concat([wf.scheduled_step, wf.published_step, wf.archived_step]); + +const extractAllStepIds = (wf: WorkflowContracts.IWorkflowContract): ReadonlyArray => + extractAllSteps(wf).map(s => s.id); diff --git a/src/commands/importExportEntities/entityDefinition.ts b/src/commands/importExportEntities/entityDefinition.ts index c22062fa..f445b66c 100644 --- a/src/commands/importExportEntities/entityDefinition.ts +++ b/src/commands/importExportEntities/entityDefinition.ts @@ -39,6 +39,8 @@ export type ImportContext = Readonly<{ >; contentTypeIdsWithElementsByOldIds: ReadonlyMap>; contentItemIdsByOldIds: IdsMap; + workflowIdsByOldIds: IdsMap; + worfklowStepsIdsByOldIds: IdsMap; }>; type IdsMap = ReadonlyMap; diff --git a/src/constants/ids.ts b/src/constants/ids.ts new file mode 100644 index 00000000..a3f2e2db --- /dev/null +++ b/src/constants/ids.ts @@ -0,0 +1 @@ +export const emptyId = "00000000-0000-0000-0000-000000000000";