Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add import languages #8

Merged
merged 1 commit into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/commands/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { collectionsEntity } from "./importExportEntities/entities/collections.j
import { contentItemsExportEntity } from "./importExportEntities/entities/contentItems.js";
import { contentTypesExportEntity } from "./importExportEntities/entities/contentTypes.js";
import { contentTypesSnippetsExportEntity } from "./importExportEntities/entities/contentTypesSnippets.js";
import { languagesExportEntity } from "./importExportEntities/entities/languages.js";
import { languagesEntity } from "./importExportEntities/entities/languages.js";
import { languageVariantsExportEntity } from "./importExportEntities/entities/languageVariants.js";
import { previewUrlsExportEntity } from "./importExportEntities/entities/previewUrls.js";
import { rolesExportEntity } from "./importExportEntities/entities/roles.js";
Expand Down Expand Up @@ -51,7 +51,7 @@ const entityDefinitions: ReadonlyArray<EntityDefinition<any>> = [
collectionsEntity,
spacesExportEntity,
taxonomiesExportEntity,
languagesExportEntity,
languagesEntity,
previewUrlsExportEntity,
rolesExportEntity,
workflowsExportEntity,
Expand Down Expand Up @@ -88,7 +88,7 @@ const exportEntities = async (params: ExportEntitiesParams): Promise<void> => {
zip.file(`${def.name}.json`, result);
}
catch (err) {
console.error(`Failed to export entity ${def.name} due to error ${err}. Stopping export...`);
console.error(`Failed to export entity ${def.name} due to error ${JSON.stringify(err)}. Stopping export...`);
process.exit(1);
}
}));
Expand Down
6 changes: 5 additions & 1 deletion src/commands/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import JSZip from "jszip";
import { RegisterCommand } from "../types/yargs.js";
import { serially } from "../utils/requests.js";
import { collectionsEntity } from "./importExportEntities/entities/collections.js";
import { languagesEntity } from "./importExportEntities/entities/languages.js";
import { EntityDefinition, ImportContext } from "./importExportEntities/entityDefinition.js";

export const register: RegisterCommand = yargs => yargs.command({
Expand Down Expand Up @@ -33,6 +34,7 @@ export const register: RegisterCommand = yargs => yargs.command({
// Keep in mind that there are dependencies between entities so the order is important.
const entityDefinitions: ReadonlyArray<EntityDefinition<any>> = [
collectionsEntity,
languagesEntity
];

type ImportEntitiesParams = Readonly<{
Expand All @@ -48,10 +50,12 @@ const importEntities = async (params: ImportEntitiesParams) => {
apiKey: params.apiKey,
});


console.log("Importing entities...");

let context: ImportContext = {
collectionIdsByOldIds: new Map(),
languageIdsByOldIds: new Map()
};

await serially(entityDefinitions.map(def => async () => {
Expand All @@ -69,7 +73,7 @@ const importEntities = async (params: ImportEntitiesParams) => {
console.log(`All entities were successfully imported into environment ${params.environmentId}.`);
}
catch (err) {
console.error(`Failed to import entity ${def.name} due to error ${err}. Stopping import...`);
console.error(`Failed to import entity ${def.name} due to error ${JSON.stringify(err)}. Stopping import...`);
process.exit(1);
}
}));
Expand Down
147 changes: 143 additions & 4 deletions src/commands/importExportEntities/entities/languages.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,150 @@
import { LanguageContracts } from "@kontent-ai/management-sdk";
import { LanguageContracts, LanguageModels, ManagementClient } from "@kontent-ai/management-sdk";

import { serially } from "../../../utils/requests.js";
import { notNull } from "../../../utils/typeguards.js";
import { EntityDefinition } from "../entityDefinition.js";

export const languagesExportEntity: EntityDefinition<ReadonlyArray<LanguageContracts.ILanguageModelContract>> = {
const defaultLanguageId = '00000000-0000-0000-0000-000000000000';

export const languagesEntity: EntityDefinition<ReadonlyArray<LanguageContracts.ILanguageModelContract>> = {
name: "languages",
fetchEntities: client => client.listLanguages().toAllPromise().then(res => res.data.items.map(l => l._raw)),
serializeEntities: collections => JSON.stringify(collections),
importEntities: () => { throw new Error("Not supported yet.")},
deserializeEntities: () => { throw new Error("Not supported yet.")},
deserializeEntities: serialized => JSON.parse(serialized),
importEntities: async (client, entities, context) => {
const importDefaultLanguage = entities.find(l => l.id === defaultLanguageId) as LanguageContracts.ILanguageModelContract;
const projectDefaultLangauge = await client.viewLanguage().byLanguageId(defaultLanguageId).toPromise().then(res => res.data);

// Order is important
await updateProjectLanguage(client, projectDefaultLangauge, importDefaultLanguage);
await importLanguagesToProject(client, entities);

const projectLanguages = await client.listLanguages().toAllPromise().then(res => res.data.items);

await updateFallbackLanguages(client, projectLanguages, entities);

return {
...context,
languageIdsByOldIds: new Map(entities
.map(importLanguage => {
const projectLanguage = projectLanguages.find(lang => lang.codename === importLanguage.codename);

return projectLanguage ? [importLanguage.id, projectLanguage.id] as const : null;
})
.filter(notNull)
)
}
},
};

const createReplaceCodenameOperation = (codename: string): LanguageModels.IModifyLanguageData => ({
'op': 'replace',
'property_name': 'codename',
'value': codename
})

const createReplaceNameOperation = (name: string): LanguageModels.IModifyLanguageData => ({
'op': 'replace',
'property_name': 'name',
'value': name
})

const createReplaceFallbackLanguageOperation = (codename: string): LanguageModels.IModifyLanguageData => ({
'op': 'replace',
'property_name': 'fallback_language',
'value': { codename }
})

const createReplaceIsActiveOperation = (isActive: boolean): LanguageModels.IModifyLanguageData => ({
'op': 'replace',
'property_name': 'is_active',
'value': isActive
})


const updateProjectLanguage = async (client: ManagementClient, projectLanguage: LanguageModels.LanguageModel, importLanguage: LanguageContracts.ILanguageModelContract) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the async keyword here.

const operations: LanguageModels.IModifyLanguageData[] = [];

if (projectLanguage.codename !== importLanguage.codename) {
operations.push(createReplaceCodenameOperation(importLanguage.codename));
}

if (projectLanguage.name !== importLanguage.name) {
operations.push(createReplaceNameOperation(importLanguage.name));
}

if (projectLanguage.id !== defaultLanguageId && !projectLanguage.isActive) {
operations.push(createReplaceIsActiveOperation(true));
}

if (operations.length > 0) {
return client
.modifyLanguage()
.byLanguageCodename(projectLanguage.codename)
.withData(operations)
.toPromise();
}

return Promise.resolve();
}

const importLanguagesToProject = async (client: ManagementClient, importLanguages: ReadonlyArray<LanguageContracts.ILanguageModelContract>) => {
const projectLanguages = await client.listLanguages().toAllPromise().then(res => res.data.items);

await serially(importLanguages
.filter(l => l.id !== defaultLanguageId)
.map(importLanguage => () => {
const projectLanguage = projectLanguages.find(l => l.codename === importLanguage.codename);

const otherProjectLanguageMatchOnName = projectLanguages.find(l => l.name === importLanguage.name && l.codename !== importLanguage.codename);

if (otherProjectLanguageMatchOnName) {
throw new Error(`Could not update name of the language with codename ${importLanguage.codename}. The other language with codename ${otherProjectLanguageMatchOnName.codename} has the same name. Fix your languages names to continue`);
}

if (projectLanguage) {
return updateProjectLanguage(client, projectLanguage, importLanguage)
}

return client
.addLanguage()
.withData({
name: importLanguage.name,
codename: importLanguage.codename,
is_active: true,
external_id: importLanguage.external_id ?? `${importLanguage.codename}`.slice(0, 50),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you put codename into the template string, codename is a string on its own. 😄

})
.toPromise();
}))
}


const updateFallbackLanguages = async (client: ManagementClient, projectLanguages: LanguageModels.LanguageModel[], importLanguages: ReadonlyArray<LanguageContracts.ILanguageModelContract>) => {
await serially(importLanguages
.filter(lang => lang.id !== defaultLanguageId)
.map(importLanguage => {
const projectLanguage = projectLanguages.find(l => l.codename === importLanguage.codename);
if (!projectLanguage) {
throw new Error(`Could not find the language with codename ${importLanguage.codename} in the project.`);
}
return [importLanguage, projectLanguage] as const;
})
.map(([importLanguage, projectLanguage]) => () => {
const projectFallbackLanguage = projectLanguages.find(l => l.id === projectLanguage.fallbackLanguage?.id);
const importFallbackLanguage = importLanguages.find(l => l.id === importLanguage.fallback_language?.id);

if (projectFallbackLanguage && importFallbackLanguage && projectFallbackLanguage.codename !== importFallbackLanguage.codename) {
return client
.modifyLanguage()
.byLanguageCodename(importLanguage.codename)
.withData([
createReplaceFallbackLanguageOperation(importFallbackLanguage.codename)
])
.toPromise();
}

return Promise.resolve();
})
)

}
1 change: 1 addition & 0 deletions src/commands/importExportEntities/entityDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export type EntityDefinition<T> = Readonly<{

export type ImportContext = Readonly<{
collectionIdsByOldIds: ReadonlyMap<string, string>;
languageIdsByOldIds: ReadonlyMap<string, string>;
}>;