Skip to content

Commit

Permalink
Add support for importing language variants
Browse files Browse the repository at this point in the history
Change fallback multi-choice option externalId so it is unique across multiple types
  • Loading branch information
JiriLojda committed Jan 8, 2024
1 parent ba50e12 commit 484fcb0
Show file tree
Hide file tree
Showing 11 changed files with 558 additions and 83 deletions.
4 changes: 2 additions & 2 deletions src/commands/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { contentItemsExportEntity } from "./importExportEntities/entities/conten
import { contentTypesEntity } from "./importExportEntities/entities/contentTypes.js";
import { contentTypesSnippetsEntity } from "./importExportEntities/entities/contentTypesSnippets.js";
import { languagesEntity } from "./importExportEntities/entities/languages.js";
import { languageVariantsExportEntity } from "./importExportEntities/entities/languageVariants.js";
import { languageVariantsEntity } from "./importExportEntities/entities/languageVariants.js";
import { previewUrlsExportEntity } from "./importExportEntities/entities/previewUrls.js";
import { rolesExportEntity } from "./importExportEntities/entities/roles.js";
import { spacesExportEntity } from "./importExportEntities/entities/spaces.js";
Expand Down Expand Up @@ -60,7 +60,7 @@ const entityDefinitions: ReadonlyArray<EntityExportDefinition<any>> = [
contentTypesSnippetsEntity,
contentTypesEntity,
contentItemsExportEntity,
languageVariantsExportEntity,
languageVariantsEntity,
assetFoldersEntity,
assetsEntity,
];
Expand Down
10 changes: 6 additions & 4 deletions src/commands/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
updateItemAndTypeReferencesInSnippetsImportEntity,
} from "./importExportEntities/entities/contentTypesSnippets.js";
import { languagesEntity } from "./importExportEntities/entities/languages.js";
import { languageVariantsEntity } from "./importExportEntities/entities/languageVariants.js";
import { taxonomiesEntity } from "./importExportEntities/entities/taxonomies.js";
import { workflowsEntity } from "./importExportEntities/entities/workflows.js";
import { EntityImportDefinition, ImportContext } from "./importExportEntities/entityDefinition.js";
Expand Down Expand Up @@ -59,6 +60,7 @@ const entityDefinitions: ReadonlyArray<EntityImportDefinition<any>> = [
updateItemAndTypeReferencesInSnippetsImportEntity,
updateItemAndTypeReferencesInTypesImportEntity,
workflowsEntity,
languageVariantsEntity,
];

type ImportEntitiesParams = Readonly<{
Expand Down Expand Up @@ -105,9 +107,9 @@ const createInitialContext = (): ImportContext => ({
taxonomyTermIdsByOldIds: new Map(),
assetFolderIdsByOldIds: new Map(),
assetIdsByOldIds: new Map(),
contentTypeSnippetIdsWithElementsByOldIds: new Map(),
contentItemIdsByOldIds: new Map(),
contentTypeIdsWithElementsByOldIds: new Map(),
contentTypeSnippetContextByOldIds: new Map(),
contentItemContextByOldIds: new Map(),
contentTypeContextByOldIds: new Map(),
workflowIdsByOldIds: new Map(),
worfklowStepsIdsByOldIds: new Map(),
worfklowStepsIdsWithTransitionsByOldIds: new Map(),
});
7 changes: 5 additions & 2 deletions src/commands/importExportEntities/entities/contentItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export const contentItemsExportEntity: EntityDefinition<ReadonlyArray<ContentIte

return {
...context,
contentItemIdsByOldIds: new Map(zip(fileItems, projectItems).map(([fItem, pItem]) => [fItem.id, pItem.id])),
contentItemContextByOldIds: new Map(
zip(fileItems, projectItems)
.map(([fItem, pItem]) => [fItem.id, { selfId: pItem.id, oldTypeId: fItem.type.id ?? "" }]),
),
};
},
};
Expand All @@ -28,7 +31,7 @@ const createImportItemFetcher =
.addContentItem()
.withData({
...fileItem,
type: { id: context.contentTypeIdsWithElementsByOldIds.get(fileItem.type.id ?? "")?.selfId },
type: { id: context.contentTypeContextByOldIds.get(fileItem.type.id ?? "")?.selfId },
collection: { id: context.collectionIdsByOldIds.get(fileItem.collection.id ?? "") },
external_id: fileItem.external_id ?? fileItem.codename,
})
Expand Down
84 changes: 72 additions & 12 deletions src/commands/importExportEntities/entities/contentTypes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ContentTypeContracts, ManagementClient } from "@kontent-ai/management-sdk";
import { ContentTypeContracts, ContentTypeElements, ManagementClient } from "@kontent-ai/management-sdk";

import { zip } from "../../../utils/array.js";
import { serially } from "../../../utils/requests.js";
import { MapValues } from "../../../utils/types.js";
import { EntityDefinition, EntityImportDefinition, ImportContext } from "../entityDefinition.js";
import { createPatchItemAndTypeReferencesInTypeElement, createTransformTypeElement } from "./utils/typeElements.js";

Expand All @@ -18,15 +19,7 @@ export const contentTypesEntity: EntityDefinition<ReadonlyArray<ContentTypeContr

return {
...context,
contentTypeIdsWithElementsByOldIds: new Map(
zip(fileTypes, projectTypes)
.map(([fileType, projectType]) => {
const elementIdEntries = zip(fileType.elements, projectType.elements)
.map(([fileEl, projectEl]) => [fileEl.id ?? "", projectEl.id ?? ""] as const);

return [fileType.id, { selfId: projectType.id, elementIdsByOldIds: new Map(elementIdEntries) }];
}),
),
contentTypeContextByOldIds: new Map(zip(fileTypes, projectTypes).map(createMakeTypeContextByOldIdEntry(context))),
};
},
deserializeEntities: JSON.parse,
Expand All @@ -47,6 +40,72 @@ type InsertTypeParams = Readonly<{
context: ImportContext;
}>;

const createMakeTypeContextByOldIdEntry = (context: ImportContext) =>
(
[fileType, projectType]: readonly [
ContentTypeContracts.IContentTypeContract,
ContentTypeContracts.IAddContentTypeResponseContract,
],
): readonly [string, MapValues<ImportContext["contentTypeContextByOldIds"]>] => {
const elementIdEntries = zip(fileType.elements, projectType.elements)
.flatMap(([fileEl, projectEl]) => {
if (fileEl.type === "snippet") {
const typedEl = fileEl as unknown as ContentTypeElements.ISnippetElement;
return [...context.contentTypeSnippetContextByOldIds.get(typedEl.snippet.id ?? "")?.elementIdsByOldIds ?? []];
}

return [[fileEl.id ?? "", projectEl.id ?? ""] as const];
});

return [fileType.id, {
selfId: projectType.id,
elementIdsByOldIds: new Map(elementIdEntries),
elementTypeByOldIds: new Map(
fileType.elements
.flatMap(el => {
if (el.type === "snippet") {
const typedEl = el as unknown as ContentTypeElements.ISnippetElement;
return [
...context.contentTypeSnippetContextByOldIds.get(typedEl.snippet.id ?? "")
?.elementTypeByOldIds ?? [],
];
}

return [[el.id ?? "", el.type]];
}),
),
multiChoiceOptionIdsByOldIdsByOldElementId: new Map(
fileType.elements
.flatMap(el => {
switch (el.type) {
case "snippet": {
const typedEl = el as unknown as ContentTypeElements.ISnippetElement;
return [
...context.contentTypeSnippetContextByOldIds.get(typedEl.snippet.id ?? "")
?.multiChoiceOptionIdsByOldIdsByOldElementId ?? [],
];
}
case "multiple_choice": {
const typedEl = el as unknown as ContentTypeElements.IMultipleChoiceElement;
const typedProjectEl = projectType.elements.find(e =>
e.id === el.id
) as ContentTypeElements.IMultipleChoiceElement;

return [[
el.id ?? "",
new Map(
zip(typedEl.options, typedProjectEl.options).map(([fO, pO]) => [fO.id ?? "", pO.id ?? ""]),
),
]];
}
default:
return [];
}
}),
),
}];
};

const createInsertTypeFetcher =
(params: InsertTypeParams) => (type: ContentTypeContracts.IContentTypeContract) => async () =>
params.client
Expand Down Expand Up @@ -79,10 +138,11 @@ type UpdateTypeParams = Readonly<{
const createUpdateTypeItemReferencesFetcher =
(params: UpdateTypeParams) => (type: ContentTypeContracts.IContentTypeContract) => () => {
const patchOps = type.elements
.filter(el => el.type !== "snippet") // We don't need to update snippet elements and snippets are expanded (not present) in the elementIdsByOldIds context
.flatMap(
createPatchItemAndTypeReferencesInTypeElement(
params.context,
c => c.contentTypeIdsWithElementsByOldIds.get(type.id)?.elementIdsByOldIds,
params.context.contentTypeContextByOldIds.get(type.id)?.elementIdsByOldIds,
),
);

Expand All @@ -92,7 +152,7 @@ const createUpdateTypeItemReferencesFetcher =

return params.client
.modifyContentType()
.byTypeId(params.context.contentTypeIdsWithElementsByOldIds.get(type.id)?.selfId ?? "")
.byTypeCodename(type.codename)
.withData(patchOps)
.toPromise();
};
51 changes: 41 additions & 10 deletions src/commands/importExportEntities/entities/contentTypesSnippets.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ContentTypeSnippetContracts, ManagementClient } from "@kontent-ai/management-sdk";
import { ContentTypeElements, ContentTypeSnippetContracts, ManagementClient } from "@kontent-ai/management-sdk";

import { zip } from "../../../utils/array.js";
import { serially } from "../../../utils/requests.js";
import { MapValues } from "../../../utils/types.js";
import { EntityDefinition, EntityImportDefinition, ImportContext } from "../entityDefinition.js";
import { createPatchItemAndTypeReferencesInTypeElement, createTransformTypeElement } from "./utils/typeElements.js";

Expand All @@ -21,14 +22,12 @@ export const contentTypesSnippetsEntity: EntityDefinition<

return {
...context,
contentTypeSnippetIdsWithElementsByOldIds: new Map(
contentTypeSnippetContextByOldIds: new Map(
zip(fileSnippets, projectSnippets)
.map(([fileSnippet, projectSnippet]) => {
const elementIdEntries = zip(fileSnippet.elements, projectSnippet.elements)
.map(([fileEl, projectEl]) => [fileEl.id ?? "", projectEl.id ?? ""] as const);

return [fileSnippet.id, { selfId: projectSnippet.id, elementIdsByOldIds: new Map(elementIdEntries) }];
}),
.map(makeSnippetContextByOldIdEntry),
),
elementTypesByOldElementIdsByOldSnippetIds: new Map(
fileSnippets.map(snippet => [snippet.id, new Map(snippet.elements.map(el => [el.id ?? "", el.type]))]),
),
};
},
Expand All @@ -44,6 +43,38 @@ export const updateItemAndTypeReferencesInSnippetsImportEntity: EntityImportDefi
},
};

const makeSnippetContextByOldIdEntry = (
[fileSnippet, projectSnippet]: readonly [
ContentTypeSnippetContracts.IContentTypeSnippetContract,
ContentTypeSnippetContracts.IViewContentTypeSnippetResponseContract,
],
): readonly [string, MapValues<ImportContext["contentTypeSnippetContextByOldIds"]>] => {
const elementIdEntries = zip(fileSnippet.elements, projectSnippet.elements)
.map(([fileEl, projectEl]) => [fileEl.id ?? "", projectEl.id ?? ""] as const);

return [fileSnippet.id, {
selfId: projectSnippet.id,
elementIdsByOldIds: new Map(elementIdEntries),
elementTypeByOldIds: new Map(fileSnippet.elements.map(el => [el.id ?? "", el.type])),
multiChoiceOptionIdsByOldIdsByOldElementId: new Map(
fileSnippet.elements
.flatMap(el => {
if (el.type !== "multiple_choice") {
return [];
}

const typedEl = el as ContentTypeElements.IMultipleChoiceElement;
const projectTypedEl = projectSnippet.elements
.find(e => e.id === el.id) as ContentTypeElements.IMultipleChoiceElement;
const multiChoiceOptionEntries = zip(typedEl.options, projectTypedEl.options)
.map(([fO, pO]) => [fO.id ?? "", pO.id ?? ""] as const);

return [[el.id ?? "", new Map(multiChoiceOptionEntries)]];
}),
),
}];
};

type InsertSnippetParams = Readonly<{
client: ManagementClient;
context: ImportContext;
Expand Down Expand Up @@ -81,7 +112,7 @@ const createUpdateSnippetItemAndTypeReferencesFetcher =
.flatMap(
createPatchItemAndTypeReferencesInTypeElement(
params.context,
c => c.contentTypeSnippetIdsWithElementsByOldIds.get(snippet.id)?.elementIdsByOldIds,
params.context.contentTypeSnippetContextByOldIds.get(snippet.id)?.elementIdsByOldIds,
),
);

Expand All @@ -91,7 +122,7 @@ const createUpdateSnippetItemAndTypeReferencesFetcher =

return params.client
.modifyContentTypeSnippet()
.byTypeId(params.context.contentTypeSnippetIdsWithElementsByOldIds.get(snippet.id)?.selfId ?? "")
.byTypeCodename(snippet.codename)
.withData(patchOps)
.toPromise();
};
Loading

0 comments on commit 484fcb0

Please sign in to comment.