From 96f242ccdd49a3c636653274bd5c7541847d714e Mon Sep 17 00:00:00 2001 From: Antony Date: Mon, 8 Apr 2024 15:16:50 +0530 Subject: [PATCH 1/2] Feat: Implementation of Variant Entry Import Module - Initial Commit --- package-lock.json | 90 ++++----- .../contentstack-import/src/config/index.ts | 10 +- .../src/types/default-config.ts | 8 + .../src/fs-utility/core.ts | 12 +- .../contentstack-variants/src/import/index.ts | 4 +- .../src/import/project.ts | 2 +- .../src/import/variant-entries.ts | 173 ++++++++++++++++++ .../src/messages/index.ts | 11 +- .../src/types/import-config.ts | 14 +- .../contentstack-variants/src/types/index.ts | 1 + .../src/types/variant-api-adapter.ts | 8 +- .../src/types/variant-entry.ts | 34 ++++ .../src/utils/adapter-helper.ts | 6 +- .../src/utils/variant-api-adapter.ts | 44 ++++- packages/contentstack-variants/tsconfig.json | 6 +- 15 files changed, 359 insertions(+), 64 deletions(-) create mode 100644 packages/contentstack-variants/src/import/variant-entries.ts create mode 100644 packages/contentstack-variants/src/types/variant-entry.ts diff --git a/package-lock.json b/package-lock.json index a303d48551..80ca57248a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6556,9 +6556,9 @@ } }, "node_modules/@slack/logger/node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", + "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", "dependencies": { "undici-types": "~5.26.4" } @@ -6921,9 +6921,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -8165,9 +8165,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1592.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1592.0.tgz", - "integrity": "sha512-iwmS46jOEHMNodfrpNBJ5eHwjKAY05t/xYV2cp+KyzMX2yGgt2/EtWWnlcoMGBKR31qKTsjMj5ZPouC9/VeDOA==", + "version": "2.1594.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1594.0.tgz", + "integrity": "sha512-ZvJ63Vm/ZuygGuO19n1PjPkyo4OcKQzgK62kAhsp4SUBDMYuemOXHpIH+ORFOjO8Js7exoqHtNS4p9fHt6cW2Q==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -8654,9 +8654,9 @@ } }, "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", "dev": true, "dependencies": { "semver": "^7.0.0" @@ -8911,9 +8911,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001605", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", - "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", + "version": "1.0.30001606", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001606.tgz", + "integrity": "sha512-LPbwnW4vfpJId225pwjZJOgX1m9sGfbw/RKJvw/t0QhYOOaTXHvkjVGFGPpvwEzufrjvTlsULnVTxdy4/6cqkg==", "dev": true, "funding": [ { @@ -10434,9 +10434,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.726", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.726.tgz", - "integrity": "sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==", + "version": "1.4.729", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz", + "integrity": "sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA==", "dev": true }, "node_modules/elegant-spinner": { @@ -11087,16 +11087,16 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, - "node_modules/eslint-config-oclif-typescript/node_modules/eslint-config-xo": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.44.0.tgz", - "integrity": "sha512-YG4gdaor0mJJi8UBeRJqDPO42MedTWYMaUyucF5bhm2pi/HS98JIxfFQmTLuyj6hGpQlAazNfyVnn7JuDn+Sew==", + "node_modules/eslint-config-oclif-typescript/node_modules/eslint-config-xo-space": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/eslint-config-xo-space/-/eslint-config-xo-space-0.35.0.tgz", + "integrity": "sha512-+79iVcoLi3PvGcjqYDpSPzbLfqYpNcMlhsCBRsnmDoHAn4npJG6YxmHpelQKpXM7v/EeZTUKb4e1xotWlei8KA==", "dev": true, "dependencies": { - "confusing-browser-globals": "1.0.11" + "eslint-config-xo": "^0.44.0" }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11105,16 +11105,16 @@ "eslint": ">=8.56.0" } }, - "node_modules/eslint-config-oclif-typescript/node_modules/eslint-config-xo-space": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/eslint-config-xo-space/-/eslint-config-xo-space-0.35.0.tgz", - "integrity": "sha512-+79iVcoLi3PvGcjqYDpSPzbLfqYpNcMlhsCBRsnmDoHAn4npJG6YxmHpelQKpXM7v/EeZTUKb4e1xotWlei8KA==", + "node_modules/eslint-config-oclif-typescript/node_modules/eslint-config-xo-space/node_modules/eslint-config-xo": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.44.0.tgz", + "integrity": "sha512-YG4gdaor0mJJi8UBeRJqDPO42MedTWYMaUyucF5bhm2pi/HS98JIxfFQmTLuyj6hGpQlAazNfyVnn7JuDn+Sew==", "dev": true, "dependencies": { - "eslint-config-xo": "^0.44.0" + "confusing-browser-globals": "1.0.11" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -19793,9 +19793,9 @@ } }, "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -25651,9 +25651,9 @@ "dev": true }, "packages/contentstack-audit/node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.12.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", + "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -25825,9 +25825,9 @@ } }, "packages/contentstack-audit/node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -27262,9 +27262,9 @@ "dev": true }, "packages/contentstack-launch/node_modules/@types/node": { - "version": "16.18.94", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.94.tgz", - "integrity": "sha512-X8q3DoKq8t/QhA0Rk/9wJUajxtXRDiCK+cVaONKLxpsjPhu+xX6uZuEj4UKGLQ4p0obTdFxa0cP/BMvf9mOYZA==", + "version": "16.18.95", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.95.tgz", + "integrity": "sha512-z9w+CcR7ahc7UhsKe+PGB25nmPmCERQBAdLdFHhoZ6+FfgSr7gUvdQI0eLH2t7ue8u5wKsLdde6cHKPjhC8vGg==", "dev": true }, "packages/contentstack-launch/node_modules/acorn": { @@ -27683,9 +27683,9 @@ } }, "packages/contentstack-variants/node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/packages/contentstack-import/src/config/index.ts b/packages/contentstack-import/src/config/index.ts index bd2c6d4036..2acf948c40 100644 --- a/packages/contentstack-import/src/config/index.ts +++ b/packages/contentstack-import/src/config/index.ts @@ -154,7 +154,7 @@ const config: DefaultConfig = { personalization: { importData: true, dirName: 'personalization', - importOrder: ['projects','attributes', 'audiences'], + importOrder: ['projects', 'attributes', 'audiences'], projects: { dirName: 'projects', fileName: 'projects.json', @@ -168,6 +168,14 @@ const config: DefaultConfig = { fileName: 'audiences.json', }, }, + variantEntry: { + dirName: 'variants', + fileName: 'index.json', + apiConcurrency: 5, + query: { + locale: 'en-us', + }, + }, }, languagesCode: [ 'af-za', diff --git a/packages/contentstack-import/src/types/default-config.ts b/packages/contentstack-import/src/types/default-config.ts index b2ba1f3e99..8b5af5ba86 100644 --- a/packages/contentstack-import/src/types/default-config.ts +++ b/packages/contentstack-import/src/types/default-config.ts @@ -140,6 +140,14 @@ export default interface DefaultConfig { fileName: string; }; }; + variantEntry: { + dirName: string; + fileName: string; + apiConcurrency: number; + query: { + locale: string; + } & AnyProperty; + } & AnyProperty; }; languagesCode: string[]; apis: { diff --git a/packages/contentstack-utilities/src/fs-utility/core.ts b/packages/contentstack-utilities/src/fs-utility/core.ts index f3d9e44f2d..fbd37ea924 100644 --- a/packages/contentstack-utilities/src/fs-utility/core.ts +++ b/packages/contentstack-utilities/src/fs-utility/core.ts @@ -101,7 +101,7 @@ export default class FsUtility { const indexPath = `${this.basePath}/${this.indexFileName}`; if (existsSync(indexPath)) { - indexData = JSON.parse(readFileSync(indexPath, 'utf-8')); + indexData = JSON.parse(readFileSync(indexPath, 'utf8')); } return indexData; @@ -139,7 +139,7 @@ export default class FsUtility { parse = typeof parse === 'undefined' ? true : parse; if (existsSync(filePath)) { - data = parse ? JSON.parse(readFileSync(filePath, 'utf-8')) : data; + data = parse ? JSON.parse(readFileSync(filePath, 'utf8')) : data; } return data; @@ -329,7 +329,7 @@ export default class FsUtility { const path = basePath || pResolve(this.basePath, 'metadata.json'); if (!existsSync(path)) return {}; - return JSON.parse(readFileSync(path, { encoding: 'utf-8' })); + return JSON.parse(readFileSync(path, { encoding: 'utf8' })); } /** @@ -353,7 +353,7 @@ export default class FsUtility { } const fileContent = readFileSync(pResolve(this.basePath, this.readIndexer[index]), { - encoding: 'utf-8', + encoding: 'utf8', }); resolve(this.fileExt === 'json' ? JSON.parse(fileContent) : fileContent); @@ -374,7 +374,7 @@ export default class FsUtility { } const fileContent = readFileSync(pResolve(this.basePath, this.readIndexer[this.pageInfo.after]), { - encoding: 'utf-8', + encoding: 'utf8', }); resolve(this.fileExt === 'json' ? JSON.parse(fileContent) : fileContent); @@ -396,7 +396,7 @@ export default class FsUtility { } const fileContent = readFileSync(pResolve(this.basePath, this.readIndexer[this.pageInfo.before]), { - encoding: 'utf-8', + encoding: 'utf8', }); resolve(this.fileExt === 'json' ? JSON.parse(fileContent) : fileContent); diff --git a/packages/contentstack-variants/src/import/index.ts b/packages/contentstack-variants/src/import/index.ts index 063e5585fa..4861a1ac99 100644 --- a/packages/contentstack-variants/src/import/index.ts +++ b/packages/contentstack-variants/src/import/index.ts @@ -1,10 +1,12 @@ import Project from './project'; import Attribute from './attribute'; import Audiences from './audiences'; +import VariantEntries from './variant-entries'; // NOTE Acting as namespace to avoid the same class name conflicts in other modules export const Import = { Project, Attribute, - Audiences + Audiences, + VariantEntries, }; diff --git a/packages/contentstack-variants/src/import/project.ts b/packages/contentstack-variants/src/import/project.ts index 9959638c5c..e532d2d1b5 100644 --- a/packages/contentstack-variants/src/import/project.ts +++ b/packages/contentstack-variants/src/import/project.ts @@ -2,7 +2,7 @@ import { join } from 'path'; import { existsSync, readFileSync } from 'fs'; import { PersonalizationAdapter } from '../utils'; -import { APIConfig, AnyProperty, CreateProjectInput, ImportConfig, LogType } from '../types'; +import { APIConfig, CreateProjectInput, ImportConfig, LogType } from '../types'; export default class Project extends PersonalizationAdapter { constructor(public readonly config: ImportConfig, private readonly log: LogType = console.log) { diff --git a/packages/contentstack-variants/src/import/variant-entries.ts b/packages/contentstack-variants/src/import/variant-entries.ts new file mode 100644 index 0000000000..46adf0db4c --- /dev/null +++ b/packages/contentstack-variants/src/import/variant-entries.ts @@ -0,0 +1,173 @@ +import chunk from 'lodash/chunk'; +import values from 'lodash/values'; +import entries from 'lodash/entries'; +import orderBy from 'lodash/orderBy'; +import isEmpty from 'lodash/isEmpty'; +import { join, resolve } from 'path'; +import { readFileSync, existsSync } from 'fs'; +import { FsUtility } from '@contentstack/cli-utilities'; + +import VariantAdapter, { VariantHttpClient } from '../utils/variant-api-adapter'; +import { + APIConfig, + AdapterType, + EntryDataForVariantEntries, + ImportConfig, + LogType, + VariantEntryStruct, +} from '../types'; + +export default class VariantEntries extends VariantAdapter> { + public entriesDirPath: string; + public variantEntryBasePath!: string; + public variantIdList!: { [key: string]: string }; + public personalizationconfig: ImportConfig['modules']['personalization']; + + constructor(readonly config: ImportConfig, private readonly log: LogType = console.log) { + const conf: APIConfig & AdapterType, APIConfig> = { + config, + httpClient: true, + baseURL: config.host, + Adapter: VariantHttpClient, + }; + super(Object.assign(config, conf)); + this.personalizationconfig = this.config.modules.personalization; + this.entriesDirPath = resolve(config.backupDir, config.branchName || '', config.modules.entries.dirName); + } + + /** + * This TypeScript function asynchronously imports backupDir from a JSON file and processes the entries + * for variant entries. + * @returns If the file at the specified file path exists and contains backupDir, the function will parse + * the JSON backupDir and iterate over each entry to import variant entries using the + * `importVariantEntries` method. If the `entriesForVariants` array is empty, the function will log a + * message indicating that no entries were found and return. + */ + async import() { + const filePath = resolve(this.config.backupDir, 'mapper', 'entries', 'data-for-variant-entry.json'); + const variantIdPath = join( + this.config.backupDir, + 'mapper', + 'personalization', + 'experiences', + 'variants-uid-mapping.json', + ); + + if (!existsSync(filePath)) { + this.log(this.config, this.messages.IMPORT_ENTRY_NOT_FOUND, 'info'); + return; + } + + if (!existsSync(variantIdPath)) { + this.log(this.config, this.messages.EMPTY_VARIANT_UID_DATA, 'info'); + return; + } + + const entriesForVariants = JSON.parse(readFileSync(filePath, 'utf8')) as EntryDataForVariantEntries[]; + + if (isEmpty(entriesForVariants)) { + this.log(this.config, this.messages.IMPORT_ENTRY_NOT_FOUND, 'info'); + return; + } + + // NOTE Read and store list of variant IDs + this.variantIdList = JSON.parse(readFileSync(variantIdPath, 'utf8')); + + for (const entriesForVariant of entriesForVariants) { + await this.importVariantEntries(entriesForVariant); + } + } + + /** + * The function `importVariantEntries` asynchronously imports variant entries using file system + * utility in TypeScript. + * @param {EntryDataForVariantEntries} entriesForVariant - EntryDataForVariantEntries { + */ + async importVariantEntries(entriesForVariant: EntryDataForVariantEntries) { + const variantEntry = this.config.modules.variantEntry; + const { content_type, locale, entry_uid } = entriesForVariant; + const variantEntryBasePath = join(this.entriesDirPath, content_type, locale, variantEntry.dirName, entry_uid); + const fs = new FsUtility({ basePath: variantEntryBasePath }); + + for (const _ in fs.indexFileContent) { + try { + const variantEntries = (await fs.readChunkFiles.next()) as VariantEntryStruct; + if (variantEntries) { + let apiContent = orderBy(values(variantEntries), '_version'); + await this.handleCuncurrency(apiContent, entriesForVariant); + } + } catch (error) { + this.log(this.config, error, 'error'); + } + } + } + + /** + * The function `handleConcurrency` processes variant entries in batches with a specified concurrency + * level and handles API calls for creating variant entries. + * @param {VariantEntryStruct[]} variantEntries - The `variantEntries` parameter is an array of + * `VariantEntryStruct` objects. It seems like this function is handling concurrency for processing + * these entries in batches. The function chunks the `variantEntries` array into smaller batches and + * then processes each batch asynchronously using `Promise.allSettled`. It also + * @param {EntryDataForVariantEntries} entriesForVariant - The `entriesForVariant` parameter seems to + * be an object containing the following properties: + * @returns The `handleConcurrency` function processes variant entries in batches, creating variant + * entries using the `createVariantEntry` method and handling the API response using + * `Promise.allSettled`. The function also includes logic to handle variant IDs and delays between + * batch processing. + */ + async handleCuncurrency(variantEntries: VariantEntryStruct[], entriesForVariant: EntryDataForVariantEntries) { + let batchNo = 0; + const variantEntry = this.config.modules.variantEntry; + const { content_type, locale, entry_uid } = entriesForVariant; + const batches = chunk(variantEntries, variantEntry.apiConcurrency | 5); + + if (isEmpty(batches)) return; + + for (const [, batch] of entries(batches)) { + batchNo += 1; + const allPromise = []; + const start = Date.now(); + + for (let [, variantEntry] of entries(batch)) { + // NOTE Find new variant Id by old Id + const variant_id = this.variantIdList[variantEntry.variant_id]; + // NOTE Replace all the relation data UID's + variantEntry = this.handleVariantEntryRelationalData(variantEntry); + + if (variant_id) { + const promise = this.variantInstance.createVariantEntry(variantEntry, { + locale, + entry_uid, + variant_id, + content_type_uid: content_type, + }); + + allPromise.push(promise); + } else { + this.log(this.config, this.messages.VARIANT_ID_NOT_FOUND, 'error'); + } + } + + // FIXME Handle the API response here + await Promise.allSettled(allPromise); + + // FIXME publish all the entries + this.publishVariantEntries(); + + const end = Date.now(); + const exeTime = end - start; + this.variantInstance.delay(1000 - exeTime); + } + } + + handleVariantEntryRelationalData(variantEntry: VariantEntryStruct) { + console.log('Handle/Replace variant entry relational data'); + // FIXME: Handle relational data + return variantEntry; + } + + publishVariantEntries() { + console.log('Variant entry publish'); + } +} diff --git a/packages/contentstack-variants/src/messages/index.ts b/packages/contentstack-variants/src/messages/index.ts index 67ad650216..ba4a463cd7 100644 --- a/packages/contentstack-variants/src/messages/index.ts +++ b/packages/contentstack-variants/src/messages/index.ts @@ -12,10 +12,17 @@ const migrationMsg = { IMPORT_MSG: 'Migrating ${module}...', }; -const messages: typeof errors & typeof commonMsg & typeof migrationMsg = { +const variantEntry = { + IMPORT_ENTRY_NOT_FOUND: 'Entries data not found to import variant entries', + EMPTY_VARIANT_UID_DATA: 'Empty variants entry mapper found!', + VARIANT_ID_NOT_FOUND: 'Variant ID not found', +}; + +const messages: typeof errors & typeof commonMsg & typeof migrationMsg & typeof variantEntry = { ...errors, ...commonMsg, - ...migrationMsg + ...migrationMsg, + ...variantEntry, }; /** diff --git a/packages/contentstack-variants/src/types/import-config.ts b/packages/contentstack-variants/src/types/import-config.ts index 1ebd2055d0..8fd9b16221 100644 --- a/packages/contentstack-variants/src/types/import-config.ts +++ b/packages/contentstack-variants/src/types/import-config.ts @@ -3,6 +3,10 @@ import { AnyProperty } from './utils'; export interface ImportDefaultConfig extends AnyProperty { personalizationHost: string; modules: { + entries: { + dirName: string; + fileName: string; + }; personalization: { dirName: string; importData: boolean; @@ -20,7 +24,15 @@ export interface ImportDefaultConfig extends AnyProperty { fileName: string; }; }; - }; + variantEntry: { + dirName: string; + fileName: string; + apiConcurrency: number; + query: { + locale: string; + } & AnyProperty; + } & AnyProperty; + } & AnyProperty; } export interface ImportConfig extends ImportDefaultConfig, AnyProperty { diff --git a/packages/contentstack-variants/src/types/index.ts b/packages/contentstack-variants/src/types/index.ts index 158f50c272..b4fe50ebb8 100644 --- a/packages/contentstack-variants/src/types/index.ts +++ b/packages/contentstack-variants/src/types/index.ts @@ -1,6 +1,7 @@ export * from './utils' export * from './export-config' export * from './import-config' +export * from './variant-entry' export * from './adapter-helper' export * from './variant-api-adapter'; export * from './personalization-api-adapter' \ No newline at end of file diff --git a/packages/contentstack-variants/src/types/variant-api-adapter.ts b/packages/contentstack-variants/src/types/variant-api-adapter.ts index 62f7f7a52c..d53231c1cf 100644 --- a/packages/contentstack-variants/src/types/variant-api-adapter.ts +++ b/packages/contentstack-variants/src/types/variant-api-adapter.ts @@ -1,8 +1,9 @@ -import { HttpClientOptions, HttpRequestConfig } from '@contentstack/cli-utilities'; +import { HttpClientOptions, HttpRequestConfig, HttpResponse } from '@contentstack/cli-utilities'; import { AnyProperty } from './utils'; import { ExportConfig } from './export-config'; import { AdapterHelperInterface } from './adapter-helper'; +import { CreateVariantEntryDto, CreateVariantEntryOptions, VariantEntryStruct } from './variant-entry'; export type APIConfig = HttpRequestConfig & { httpClient?: boolean; @@ -40,4 +41,9 @@ export interface VariantInterface extends AdapterHelperInterface }>; variantEntries(options: VariantsOption): Promise<{ entries?: Record[] | unknown[] } | void>; + + createVariantEntry( + input: CreateVariantEntryDto, + options: CreateVariantEntryOptions, + ): Promise>; } diff --git a/packages/contentstack-variants/src/types/variant-entry.ts b/packages/contentstack-variants/src/types/variant-entry.ts new file mode 100644 index 0000000000..52eccaf334 --- /dev/null +++ b/packages/contentstack-variants/src/types/variant-entry.ts @@ -0,0 +1,34 @@ +import { AnyProperty } from './utils'; + +export type VariantEntryStruct = { + uid: string; + title: string; + variant_id: string; + locale: string; + _version: number; + _variant: { + uid: string; + _change_set: string[]; + _base_entry_version: number; + }; +} & AnyProperty; + +export type EntryDataForVariantEntries = { + content_type: string; + locale: string; + entry_uid: string; +}; + +export type CreateVariantEntryDto = { + title: string; + _variant: { + _change_set: string[]; + }; +} & AnyProperty; + +export type CreateVariantEntryOptions = { + locale?: string; + entry_uid: string; + variant_id: string; + content_type_uid: string; +}; \ No newline at end of file diff --git a/packages/contentstack-variants/src/utils/adapter-helper.ts b/packages/contentstack-variants/src/utils/adapter-helper.ts index de19d8730a..08b3e5d76e 100644 --- a/packages/contentstack-variants/src/utils/adapter-helper.ts +++ b/packages/contentstack-variants/src/utils/adapter-helper.ts @@ -4,8 +4,8 @@ import { HttpClient, HttpClientOptions, HttpRequestConfig } from '@contentstack/ import messages, { $t } from '../messages'; import { APIConfig, AdapterHelperInterface } from '../types'; -export class AdapterHelper implements AdapterHelperInterface { - public readonly config: T; +export class AdapterHelper implements AdapterHelperInterface { + public readonly config: C; public readonly $t: typeof $t public readonly apiClient: ApiClient; public readonly messages: typeof messages; @@ -13,7 +13,7 @@ export class AdapterHelper implements AdapterHelperInterface extends AdapterHelper implements VariantInterface { +export class VariantHttpClient extends AdapterHelper implements VariantInterface { constructor(config: APIConfig, options?: HttpClientOptions) { super(config, options); const baseURL = config.baseURL?.includes('http') ? `${config.baseURL}/v3` : `https://${config.baseURL}/v3`; @@ -119,6 +125,32 @@ export class VariantHttpClient extends AdapterHelper implement if (returnResult) return { entries }; } + + /** + * The function `createVariantEntry` creates a new variant entry using the provided input and + * options. + * @param {CreateVariantEntryDto} input - The `input` parameter in the `createVariantEntry` function + * is of type `CreateVariantEntryDto`. This parameter likely contains the data needed to create a new + * variant entry, such as the fields and values for the variant entry. + * @param {CreateVariantEntryOptions} options - The `options` parameter contains the following + * properties: + * @returns The function `createVariantEntry` is returning a POST request to the specified endpoint + * with the input data provided in the `CreateVariantEntryDto` parameter. The response is expected to + * be of type `VariantEntryStruct`. + */ + createVariantEntry(input: CreateVariantEntryDto, options: CreateVariantEntryOptions) { + const variantConfig = (this.config as ImportConfig).modules.variantEntry; + const { locale = variantConfig.query.locale || 'en-us', variant_id, entry_uid, content_type_uid } = options; + let endpoint = `content_types/${content_type_uid}/entries/${entry_uid}/variants/${variant_id}?locale=${locale}`; + + const query = this.constructQuery(omit(variantConfig.query, ['locale'])); + + if (query) { + endpoint = endpoint.concat(query); + } + + return this.apiClient.post(endpoint, input); + } } export class VariantManagementSDK @@ -126,7 +158,7 @@ export class VariantManagementSDK implements VariantInterface { public override apiClient!: ContentstackClient; - + async init(): Promise { this.apiClient = await managementSDKClient(this.config); } @@ -141,6 +173,11 @@ export class VariantManagementSDK return { entries: [{}] }; } + createVariantEntry(input: CreateVariantEntryDto, options: CreateVariantEntryOptions): Promise> { + // FIXME placeholder + return new HttpClient().post('/', input); + } + constructQuery(query: Record): string | void {} async delay(ms: number): Promise {} @@ -148,6 +185,7 @@ export class VariantManagementSDK export class VariantAdapter { protected variantInstance; + public readonly messages: typeof messages; constructor(config: ContentstackConfig & AnyProperty & AdapterType); constructor(config: APIConfig & AdapterType, options?: HttpClientOptions); @@ -162,6 +200,8 @@ export class VariantAdapter { const { Adapter, ...restConfig } = config; this.variantInstance = new Adapter(restConfig); } + + this.messages = messages; } } diff --git a/packages/contentstack-variants/tsconfig.json b/packages/contentstack-variants/tsconfig.json index 229aed464b..6fb36b3e72 100644 --- a/packages/contentstack-variants/tsconfig.json +++ b/packages/contentstack-variants/tsconfig.json @@ -8,6 +8,10 @@ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "strict": true, /* Enable all strict type-checking options. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "lib": [ + "ES2019", + "es2020.promise" + ], } } \ No newline at end of file From 8eed1e85c0e34805773e7d2ff8796647ca42c97d Mon Sep 17 00:00:00 2001 From: Antony Date: Wed, 10 Apr 2024 12:46:25 +0530 Subject: [PATCH 2/2] Code clean --- .../src/import/variant-entries.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/contentstack-variants/src/import/variant-entries.ts b/packages/contentstack-variants/src/import/variant-entries.ts index 46adf0db4c..c68340e768 100644 --- a/packages/contentstack-variants/src/import/variant-entries.ts +++ b/packages/contentstack-variants/src/import/variant-entries.ts @@ -5,16 +5,16 @@ import orderBy from 'lodash/orderBy'; import isEmpty from 'lodash/isEmpty'; import { join, resolve } from 'path'; import { readFileSync, existsSync } from 'fs'; -import { FsUtility } from '@contentstack/cli-utilities'; +import { FsUtility, HttpResponse } from '@contentstack/cli-utilities'; import VariantAdapter, { VariantHttpClient } from '../utils/variant-api-adapter'; import { + LogType, APIConfig, AdapterType, - EntryDataForVariantEntries, ImportConfig, - LogType, VariantEntryStruct, + EntryDataForVariantEntries, } from '../types'; export default class VariantEntries extends VariantAdapter> { @@ -149,11 +149,11 @@ export default class VariantEntries extends VariantAdapter>[]) { + // FIXME: Handle variant entry publish console.log('Variant entry publish'); + return resultSet; } }