Skip to content

Commit

Permalink
Add webSpotlight to syncModel including advanced diff
Browse files Browse the repository at this point in the history
  • Loading branch information
JiriLojda committed Aug 8, 2024
1 parent 9b6e736 commit 69cf9d7
Show file tree
Hide file tree
Showing 21 changed files with 389 additions and 86 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"chalk": "^5.3.0",
"node-stream-zip": "^1.15.0",
"open": "^10.1.0",
"ts-pattern": "^5.2.0",
"yargs": "^17.7.2",
"zod": "^3.23.8"
},
Expand Down
1 change: 1 addition & 0 deletions src/modules/sync/constants/filename.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const contentTypesFileName = "contentTypes.json";
export const contentTypeSnippetsFileName = "contentTypeSnippets.json";
export const taxonomiesFileName = "taxonomyGroups.json";
export const webSpotlightFileName = "webSpotlight.json";
7 changes: 7 additions & 0 deletions src/modules/sync/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
transformTaxonomyToAddModel,
transformTypeToAddModel,
} from "./diff/transformToAddModel.js";
import { webSpotlightHandler } from "./diff/webSpotlight.js";
import { DiffModel } from "./types/diffModel.js";
import { FileContentModel } from "./types/fileContentModel.js";
import { PatchOperation } from "./types/patchOperation.js";
Expand Down Expand Up @@ -52,11 +53,17 @@ export const diff = (params: DiffParams): DiffModel => {
taxonomyGroupHandler,
);

const webSpotlightDiffModel = webSpotlightHandler(
params.sourceEnvModel.webSpotlight,
params.targetEnvModel.webSpotlight,
);

return {
// All the arrays are mutable in the SDK (even though they shouldn't) and readonly in our models. Unfortunately, TS doesn't allow casting it without casting to unknown first.
taxonomyGroups: mapAdded(taxonomyDiffModel, transformTaxonomyToAddModel),
contentTypeSnippets: mapAdded(snippetsDiffModel, transformSnippetToAddModel(params)),
contentTypes: mapAdded(typesDiffModel, transformTypeToAddModel(params)),
webSpotlight: webSpotlightDiffModel,
};
};

Expand Down
21 changes: 21 additions & 0 deletions src/modules/sync/diff/webSpotlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { match } from "ts-pattern";

import { WebSpotlightDiffModel } from "../types/diffModel.js";
import { WebSpotlightSyncModel } from "../types/syncModel.js";

export const webSpotlightHandler = (
source: WebSpotlightSyncModel,
target: WebSpotlightSyncModel,
): WebSpotlightDiffModel =>
match({ source: source.enabled, target: target.enabled })
.with({ source: true, target: true }, () =>
source.root_type?.codename === target.root_type?.codename
? { change: "none" } as const
: { change: "changeRootType", rootTypeCodename: source.root_type?.codename ?? "" } as const)
.with({ source: false, target: false }, () => ({ change: "none" } as const))
.with({ source: false, target: true }, () => ({ change: "deactivate" } as const))
.with(
{ source: true, target: false },
() => ({ change: "activate", rootTypeCodename: source.root_type?.codename ?? "" } as const),
)
.exhaustive();
27 changes: 25 additions & 2 deletions src/modules/sync/generateSyncModel.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { AssetContracts, ContentItemContracts, ManagementClient, TaxonomyContracts } from "@kontent-ai/management-sdk";
import {
AssetContracts,
ContentItemContracts,
ManagementClient,
TaxonomyContracts,
WebSpotlightContracts,
} from "@kontent-ai/management-sdk";
import chalk from "chalk";
import * as fsPromises from "fs/promises";
import * as path from "path";

import packageJson from "../../../package.json" with { type: "json" };
import { logInfo, LogOptions } from "../../log.js";
import { serializeDateForFileName } from "../../utils/files.js";
import { contentTypesFileName, contentTypeSnippetsFileName, taxonomiesFileName } from "./constants/filename.js";
import {
contentTypesFileName,
contentTypeSnippetsFileName,
taxonomiesFileName,
webSpotlightFileName,
} from "./constants/filename.js";
import { transformContentTypeModel } from "./modelTransfomers/contentTypes.js";
import { transformContentTypeSnippetsModel } from "./modelTransfomers/contentTypeSnippets.js";
import { transformTaxonomyGroupsModel } from "./modelTransfomers/taxonomyGroups.js";
import { transformWebSpotlightModel } from "./modelTransfomers/webSpotlight.js";
import { ContentTypeSnippetsWithUnionElements, ContentTypeWithUnionElements } from "./types/contractModels.js";
import { FileContentModel } from "./types/fileContentModel.js";
import { getRequiredIds } from "./utils/contentTypeHelpers.js";
Expand All @@ -19,12 +31,14 @@ import {
fetchRequiredAssets,
fetchRequiredContentItems,
fetchTaxonomies,
fetchWebSpotlight,
} from "./utils/fetchers.js";

export type EnvironmentModel = {
taxonomyGroups: ReadonlyArray<TaxonomyContracts.ITaxonomyContract>;
contentTypeSnippets: ReadonlyArray<ContentTypeSnippetsWithUnionElements>;
contentTypes: ReadonlyArray<ContentTypeWithUnionElements>;
webSpotlight: WebSpotlightContracts.IWebSpotlightStatus;
assets: ReadonlyArray<AssetContracts.IAssetModelContract>;
items: ReadonlyArray<ContentItemContracts.IContentItemModelContract>;
};
Expand All @@ -36,6 +50,8 @@ export const fetchModel = async (client: ManagementClient): Promise<EnvironmentM
) as unknown as ContentTypeSnippetsWithUnionElements[];
const taxonomies = await fetchTaxonomies(client);

const webSpotlight = await fetchWebSpotlight(client);

const allIds = [...contentTypes, ...contentTypeSnippets].reduce<{ assetIds: Set<string>; itemIds: Set<string> }>(
(previous, type) => {
const ids = getRequiredIds(type.elements);
Expand All @@ -55,6 +71,7 @@ export const fetchModel = async (client: ManagementClient): Promise<EnvironmentM
contentTypes,
contentTypeSnippets,
taxonomyGroups: taxonomies,
webSpotlight,
assets,
items,
};
Expand All @@ -64,11 +81,13 @@ export const transformSyncModel = (environmentModel: EnvironmentModel, logOption
const contentTypeModel = transformContentTypeModel(environmentModel, logOptions);
const contentTypeSnippetModel = transformContentTypeSnippetsModel(environmentModel, logOptions);
const taxonomyGroupsModel = transformTaxonomyGroupsModel(environmentModel.taxonomyGroups);
const webSpotlightModel = transformWebSpotlightModel(environmentModel);

return {
contentTypes: contentTypeModel,
contentTypeSnippets: contentTypeSnippetModel,
taxonomyGroups: taxonomyGroupsModel,
webSpotlight: webSpotlightModel,
};
};

Expand Down Expand Up @@ -108,6 +127,10 @@ export const saveSyncModel = async (params: SaveModelParams) => {
path.resolve(folderName, taxonomiesFileName),
JSON.stringify(finalModel.taxonomyGroups, null, 2),
);
await fsPromises.writeFile(
path.resolve(folderName, webSpotlightFileName),
JSON.stringify(finalModel.webSpotlight, null, 2),
);
await fsPromises.writeFile(path.resolve(folderName, "metadata.json"), JSON.stringify(finalModel.metadata, null, 2));

return folderName;
Expand Down
13 changes: 13 additions & 0 deletions src/modules/sync/modelTransfomers/webSpotlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EnvironmentModel } from "../generateSyncModel.js";
import { WebSpotlightSyncModel } from "../types/syncModel.js";

export const transformWebSpotlightModel = (environmentModel: EnvironmentModel): WebSpotlightSyncModel => {
const rootTypeCodename = environmentModel.contentTypes
.find(type => type.id === environmentModel.webSpotlight.root_type?.id)
?.codename;

return ({
enabled: environmentModel.webSpotlight.enabled,
root_type: rootTypeCodename ? { codename: rootTypeCodename } : null,
});
};
29 changes: 26 additions & 3 deletions src/modules/sync/printDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ export const printDiff = (diffModel: DiffModel, logOptions: LogOptions) => {

logInfo(logOptions, "standard", chalk.blue.bold("\nCONTENT TYPES:"));
printDiffEntity(diffModel.contentTypes, "content types", logOptions);

if (diffModel.webSpotlight.change !== "none") {
logInfo(logOptions, "standard", chalk.blue.bold("\nWEB SPOTLIGHT:"));
switch (diffModel.webSpotlight.change) {
case "activate":
logInfo(
logOptions,
"standard",
`Web Spotlight is to be activated with root type: ${chalk.green(diffModel.webSpotlight.rootTypeCodename)}`,
);
break;
case "changeRootType":
logInfo(
logOptions,
"standard",
`Web Spotlight root type is changed to: ${chalk.green(diffModel.webSpotlight.rootTypeCodename)}`,
);
break;
case "deactivate":
logInfo(logOptions, "standard", "Web Spotlight is to be deactivated");
break;
}
}
};

const printDiffEntity = (
Expand All @@ -20,7 +43,7 @@ const printDiffEntity = (
logOptions: LogOptions,
) => {
if (diffObject.added.length) {
logInfo(logOptions, "standard", `Added ${chalk.blue(entityName)}:`);
logInfo(logOptions, "standard", `${chalk.blue(entityName)} to Add:`);
diffObject.added.forEach(a => {
logInfo(logOptions, "standard", `${chalk.green(JSON.stringify(a, null, 2))}\n`);
});
Expand All @@ -29,7 +52,7 @@ const printDiffEntity = (
}

if (Array.from(diffObject.updated.values()).some(o => o.length)) {
logInfo(logOptions, "standard", `Updated ${chalk.blue(entityName)}:`);
logInfo(logOptions, "standard", `${chalk.blue(entityName)} to update:`);
Array.from(diffObject.updated.entries()).sort().forEach(([codename, value]) => {
if (value.length) {
logInfo(logOptions, "standard", `Entity codename: ${chalk.blue(codename)}`);
Expand All @@ -44,7 +67,7 @@ const printDiffEntity = (
logInfo(
logOptions,
"standard",
`Deleted ${chalk.blue(entityName)} with codenames: ${chalk.red(Array.from(diffObject.deleted).join(","))}\n`,
`${chalk.blue(entityName)} to delete with codenames: ${chalk.red(Array.from(diffObject.deleted).join(","))}\n`,
);
} else {
logInfo(logOptions, "standard", `No ${chalk.blue(entityName)} to delete.`);
Expand Down
24 changes: 21 additions & 3 deletions src/modules/sync/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { notNullOrUndefined } from "../../utils/typeguards.js";
import { RequiredCodename } from "../../utils/types.js";
import { elementTypes } from "./constants/elements.js";
import { ElementsTypes } from "./types/contractModels.js";
import { DiffModel } from "./types/diffModel.js";
import { DiffModel, WebSpotlightDiffModel } from "./types/diffModel.js";
import { getTargetCodename, PatchOperation } from "./types/patchOperation.js";

const referencingElements: ReadonlyArray<ElementsTypes> = ["rich_text", "modular_content", "subpages"];
Expand Down Expand Up @@ -43,14 +43,17 @@ export const sync = async (client: ManagementClient, diff: DiffModel, logOptions
logInfo(logOptions, "standard", "Updating content types and adding their references");
await updateContentTypesAndAddReferences(client, diff.contentTypes);

logInfo(logOptions, "standard", "Updating web spotlight");
await updateWebSpotlight(client, diff.webSpotlight);

logInfo(logOptions, "standard", "Removing content types");
await serially(Array.from(diff.contentTypes.deleted).map(c => () => deleteContentType(client, c)));

logInfo(logOptions, "standard", "Deleting content type snippets");
logInfo(logOptions, "standard", "Removing content type snippets");
await serially(Array.from(diff.contentTypeSnippets.deleted).map(c => () => deleteSnippet(client, c)));

// replace, remove, move operations
logInfo(logOptions, "standard", "Deleting content type snippets");
logInfo(logOptions, "standard", "Updating content type snippets");
await updateSnippets(client, diff.contentTypeSnippets.updated);
};

Expand Down Expand Up @@ -300,6 +303,21 @@ const transformTaxonomyOperations = (
} as unknown as TaxonomyModels.IModifyTaxonomyData;
};

const updateWebSpotlight = (client: ManagementClient, diffModel: WebSpotlightDiffModel): Promise<unknown> => {
switch (diffModel.change) {
case "none":
return Promise.resolve();
case "activate":
case "changeRootType":
return client
.activateWebSpotlight()
.withData({ root_type: { codename: diffModel.rootTypeCodename } })
.toPromise();
case "deactivate":
return client.deactivateWebSpotlight().toPromise();
}
};

const createUpdateReferenceOps = (
element: ReferencingElement,
) =>
Expand Down
8 changes: 8 additions & 0 deletions src/modules/sync/types/diffModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ export type DiffObject<AddModel> = Readonly<{
deleted: ReadonlySet<Codename>;
}>;

export type WebSpotlightDiffModel = Readonly<
| { change: "none" }
| { change: "activate"; rootTypeCodename: Codename }
| { change: "changeRootType"; rootTypeCodename: Codename }
| { change: "deactivate" }
>;

export type DiffModel = Readonly<{
taxonomyGroups: DiffObject<RequiredCodename<TaxonomyModels.IAddTaxonomyRequestModel>>;
contentTypeSnippets: DiffObject<RequiredCodename<ContentTypeSnippetModels.IAddContentTypeSnippetData>>;
contentTypes: DiffObject<RequiredCodename<ContentTypeModels.IAddContentTypeData>>;
webSpotlight: WebSpotlightDiffModel;
}>;
8 changes: 7 additions & 1 deletion src/modules/sync/types/fileContentModel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { ContentTypeSnippetsSyncModel, ContentTypeSyncModel, TaxonomySyncModel } from "./syncModel.js";
import {
ContentTypeSnippetsSyncModel,
ContentTypeSyncModel,
TaxonomySyncModel,
WebSpotlightSyncModel,
} from "./syncModel.js";

export type FileContentModel = Readonly<{
taxonomyGroups: ReadonlyArray<TaxonomySyncModel>;
contentTypeSnippets: ReadonlyArray<ContentTypeSnippetsSyncModel>;
contentTypes: ReadonlyArray<ContentTypeSyncModel>;
webSpotlight: WebSpotlightSyncModel;
}>;
6 changes: 6 additions & 0 deletions src/modules/sync/types/syncModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ContentTypeElements,
ContentTypeSnippetContracts,
TaxonomyContracts,
WebSpotlightContracts,
} from "@kontent-ai/management-sdk";

import { CodenameReference, Replace } from "../../../utils/types.js";
Expand Down Expand Up @@ -117,6 +118,11 @@ export type ContentTypeSyncModel = Replace<
}>
>;

export type WebSpotlightSyncModel = Replace<
WebSpotlightContracts.IWebSpotlightStatus,
{ root_type: Readonly<{ codename: string }> | null }
>;

export const isSyncCustomElement = (entity: unknown): entity is SyncCustomElement =>
typeof entity === "object" && entity !== null && "type" in entity && entity.type === "custom";

Expand Down
1 change: 1 addition & 0 deletions src/modules/sync/utils/diffTemplate.html
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ <h2>Modified entities</h2>
{{types_section}}
{{snippets_section}}
{{taxonomies_section}}
{{web_spotlight_section}}
</div>
</div>
<script>
Expand Down
6 changes: 6 additions & 0 deletions src/modules/sync/utils/fetchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,9 @@ export const fetchTaxonomies = (client: ManagementClient) =>
.listTaxonomies()
.toAllPromise()
.then(res => res.data.items.map(t => t._raw));

export const fetchWebSpotlight = (client: ManagementClient) =>
client
.checkWebSpotlightStatus()
.toPromise()
.then(res => res.rawData);
Loading

0 comments on commit 69cf9d7

Please sign in to comment.