diff --git a/packages/contentstack-export/src/config/index.ts b/packages/contentstack-export/src/config/index.ts index c941dd061c..d6e23f4c7d 100644 --- a/packages/contentstack-export/src/config/index.ts +++ b/packages/contentstack-export/src/config/index.ts @@ -179,8 +179,9 @@ const config: DefaultConfig = { query: { skip: 0, limit: 100, - locale: 'en-us', include_variant: false, + include_count: true, + include_publish_details: true, }, mockDataPath: './variant-mock-data.json', }, diff --git a/packages/contentstack-export/src/export/modules/entries.ts b/packages/contentstack-export/src/export/modules/entries.ts index a1695aa9e1..60877691af 100644 --- a/packages/contentstack-export/src/export/modules/entries.ts +++ b/packages/contentstack-export/src/export/modules/entries.ts @@ -75,7 +75,9 @@ export default class EntriesExport extends BaseClass { Object.assign(this.exportConfig, { project_id }), log as LogType, ); - } catch (_error) {} + } catch (error) { + log(this.exportConfig, `Failed to export variant entries ${error}`, 'error'); + } } const entryRequestOptions = this.createRequestObjects(locales, contentTypes); @@ -91,7 +93,7 @@ export default class EntriesExport extends BaseClass { log(this.exportConfig, 'Entries exported successfully', 'success'); } catch (error) { log(this.exportConfig, `Failed to export entries ${formatError(error)}`, 'error'); - throw new Error('Failed to export entries'); + // throw new Error('Failed to export entries'); } } diff --git a/packages/contentstack-export/src/types/default-config.ts b/packages/contentstack-export/src/types/default-config.ts index 4b4d1b52b6..edded90644 100644 --- a/packages/contentstack-export/src/types/default-config.ts +++ b/packages/contentstack-export/src/types/default-config.ts @@ -133,8 +133,9 @@ export default interface DefaultConfig { query: { skip: number; limit: number; - locale: string; include_variant: boolean; + include_count: boolean; + include_publish_details: boolean; } & AnyProperty; } & AnyProperty; extensions: { diff --git a/packages/contentstack-variants/src/export/variant-entries.ts b/packages/contentstack-variants/src/export/variant-entries.ts index 68637b3f1f..5be8bbc8c6 100644 --- a/packages/contentstack-variants/src/export/variant-entries.ts +++ b/packages/contentstack-variants/src/export/variant-entries.ts @@ -49,24 +49,33 @@ export default class VariantEntries extends VariantAdapter[]) => { - if (!isEmpty(variantEntries)) { + if (variantEntries?.length) { variantEntriesFs.writeIntoFile(variantEntries); } }; - await this.variantInstance - .variantEntries({ + try { + await this.variantInstance.variantEntries({ callback, getAllData: true, content_type_uid, entry_uid: entry.uid, - }) - variantEntriesFs.completeFile(true); - this.log( - this.config, - `Exported variant entries of type '${entry.title} (${entry.uid})' locale '${locale}'`, - 'info', - ); + locale, + }); + variantEntriesFs.completeFile(true); + this.log( + this.config, + `Exported variant entries of type '${entry.title} (${entry.uid})' locale '${locale}'`, + 'info', + ); + } catch (error) { + this.log( + this.config, + `Error exporting variant entries of type '${entry.title} (${entry.uid})' locale '${locale}'`, + 'error', + ); + this.log(this.config, error, 'error'); + } } } } diff --git a/packages/contentstack-variants/src/import/variant-entries.ts b/packages/contentstack-variants/src/import/variant-entries.ts index dc47f23169..7e477ba959 100644 --- a/packages/contentstack-variants/src/import/variant-entries.ts +++ b/packages/contentstack-variants/src/import/variant-entries.ts @@ -2,6 +2,8 @@ import omit from 'lodash/omit'; import chunk from 'lodash/chunk'; import entries from 'lodash/entries'; import isEmpty from 'lodash/isEmpty'; +import forEach from 'lodash/forEach'; +import indexOf from 'lodash/indexOf'; import { join, resolve } from 'path'; import { readFileSync, existsSync } from 'fs'; import { FsUtility, HttpResponse } from '@contentstack/cli-utilities'; @@ -18,6 +20,7 @@ import { ImportHelperMethodsConfig, EntryDataForVariantEntries, CreateVariantEntryDto, + PublishVariantEntryDto, } from '../types'; import { fsUtil } from '../utils'; @@ -33,8 +36,8 @@ export default class VariantEntries extends VariantAdapter; public entriesUidMapper!: Record; private installedExtensions!: Record[]; - private variantUidMapper: Record; private variantUidMapperPath!: string; + private environments!: Record; constructor( readonly config: ImportConfig & { helpers?: ImportHelperMethodsConfig }, @@ -57,8 +60,6 @@ export default class VariantEntries extends VariantAdapter; if (isEmpty(this.variantIdList)) { @@ -122,10 +123,12 @@ export default class VariantEntries extends VariantAdapter; this.assetUidMapper = (fsUtil.readFile(assetUidMapperPath, true) || {}) as Record; this.assetUrlMapper = (fsUtil.readFile(assetUrlMapperPath, true) || {}) as Record; + this.environments = (fsUtil.readFile(envPath, true) || {}) as Record; for (const entriesForVariant of entriesForVariants) { await this.importVariantEntries(entriesForVariant); } + this.log(this.config, 'All the variant-entries have been imported & published successfully', 'success'); } /** @@ -147,7 +150,7 @@ export default class VariantEntries extends VariantAdapter { - log(this.config, `Created variant entry: '${title}' of entry uid ${entryUid}`, 'info'); - this.variantUidMapper[variantUid] = response?.entry?._variant?.uid || ''; + const onSuccess = ({ response, apiData: { entryUid, variantUid }, log }: any) => { + log(this.config, `Created variant entry: '${variantUid}' of entry uid ${entryUid}`, 'info'); }; - const onReject = ({ error, apiData: { entryUid, variantUid, title }, log }: any) => { - log(this.config, `Failed to create variant entry: '${title}' of entry uid ${entryUid}`, 'error'); + + const onReject = ({ error, apiData: { entryUid, variantUid }, log }: any) => { + log(this.config, `Failed to create variant entry: '${variantUid}' of entry uid ${entryUid}`, 'error'); log(this.config, error, 'error'); }; // NOTE Find new variant Id by old Id const variant_id = this.variantIdList[variantEntry.variant_id] as string; // NOTE Replace all the relation data UID's variantEntry = this.handleVariantEntryRelationalData(contentType, variantEntry); + const changeSet = this.serializeChangeSet(variantEntry); const createVariantReq: CreateVariantEntryDto = { - title: variantEntry.title, _variant: variantEntry._variant, + ...changeSet, }; - const variantUid = variantEntry?._variant?.uid || ''; if (variant_id) { const promise = this.variantInstance.createVariantEntry( @@ -217,7 +220,7 @@ export default class VariantEntries extends VariantAdapter = {}; + if (variantEntry?._variant?._change_set?.length) { + variantEntry._variant._change_set.forEach((data: string) => { + if (variantEntry[data]) { + changeSet[data] = variantEntry[data]; + } + }); + } + return changeSet; + } + /** * The function `handleVariantEntryRelationalData` processes relational data for a variant entry * based on the provided content type and configuration helpers. @@ -309,9 +326,97 @@ export default class VariantEntries extends VariantAdapter[]) { - // FIXME: Handle variant entry publish - console.log('Variant entry publish'); - return resultSet; + /** + * Publishes variant entries in batch for a given entry UID and content type. + * @param batch - An array of VariantEntryStruct objects representing the variant entries to be published. + * @param entryUid - The UID of the entry for which the variant entries are being published. + * @param content_type - The UID of the content type of the entry. + * @returns A Promise that resolves when all variant entries have been published. + */ + async publishVariantEntries(batch: VariantEntryStruct[], entryUid: string, content_type: string) { + const allPromise = []; + for (let [, variantEntry] of entries(batch)) { + const oldVariantUid = variantEntry.variant_id || ''; + const newVariantUid = this.variantIdList[oldVariantUid] as string; + if (!newVariantUid) { + this.log(this.config, `Variant UID not found for entry '${variantEntry?.uid}'`, 'error'); + continue; + } + if (this.environments?.length) { + this.log(this.config, 'No environment found! Skipping variant entry publishing...', 'info'); + return; + } + + const onSuccess = ({ response, apiData: { entryUid, variantUid }, log }: any) => { + log(this.config, `Variant entry: '${variantUid}' of entry uid ${entryUid} published successfully!`, 'info'); + }; + const onReject = ({ error, apiData: { entryUid, variantUid }, log }: any) => { + log(this.config, `Failed to publish variant entry: '${variantUid}' of entry uid ${entryUid}`, 'error'); + log(this.config, error, 'error'); + }; + + const { environments, locales } = this.serializePublishEntries(variantEntry); + if (environments?.length === 0 || locales?.length === 0) { + continue; + } + const publishReq: PublishVariantEntryDto = { + entry: { + environments, + locales, + publish_with_base_entry: false, + variants: [{ uid: newVariantUid, version: 1 }], + }, + locale: variantEntry.locale, + version: 1, + }; + + const promise = this.variantInstance.publishVariantEntry( + publishReq, + { + entry_uid: entryUid, + content_type_uid: content_type, + }, + { + reject: onReject.bind(this), + resolve: onSuccess.bind(this), + log: this.log, + }, + ); + + allPromise.push(promise); + } + await Promise.allSettled(allPromise); + } + + /** + * Serializes the publish entries of a variant. + * @param variantEntry - The variant entry to serialize. + * @returns An object containing the serialized publish entries. + */ + serializePublishEntries(variantEntry: VariantEntryStruct): { + environments: Array; + locales: Array; + } { + const requestObject: { + environments: Array; + locales: Array; + } = { + environments: [], + locales: [], + }; + if (variantEntry.publish_details && variantEntry.publish_details?.length > 0) { + forEach(variantEntry.publish_details, (pubObject) => { + if ( + this.environments.hasOwnProperty(pubObject.environment) && + indexOf(requestObject.environments, this.environments[pubObject.environment].name) === -1 + ) { + requestObject.environments.push(this.environments[pubObject.environment].name); + } + if (pubObject.locale && indexOf(requestObject.locales, pubObject.locale) === -1) { + requestObject.locales.push(pubObject.locale); + } + }); + } + return requestObject; } } diff --git a/packages/contentstack-variants/src/types/export-config.ts b/packages/contentstack-variants/src/types/export-config.ts index 7e2e1af690..8c106d069e 100644 --- a/packages/contentstack-variants/src/types/export-config.ts +++ b/packages/contentstack-variants/src/types/export-config.ts @@ -150,8 +150,9 @@ export interface DefaultConfig { query: { skip: number; limit: number; - locale: string; include_variant: boolean; + include_count: boolean; + include_publish_details: boolean; } & AnyProperty; } & AnyProperty; personalization: { diff --git a/packages/contentstack-variants/src/types/variant-api-adapter.ts b/packages/contentstack-variants/src/types/variant-api-adapter.ts index 90dbb95cc6..e422d11210 100644 --- a/packages/contentstack-variants/src/types/variant-api-adapter.ts +++ b/packages/contentstack-variants/src/types/variant-api-adapter.ts @@ -31,6 +31,8 @@ export type VariantsOption = { returnResult?: boolean; content_type_uid: string; include_variant?: boolean; + include_count?: boolean; + include_publish_details?: boolean; callback?: (value: Record[]) => void; } & AnyProperty; diff --git a/packages/contentstack-variants/src/types/variant-entry.ts b/packages/contentstack-variants/src/types/variant-entry.ts index 52eccaf334..adf10be0bb 100644 --- a/packages/contentstack-variants/src/types/variant-entry.ts +++ b/packages/contentstack-variants/src/types/variant-entry.ts @@ -11,6 +11,15 @@ export type VariantEntryStruct = { _change_set: string[]; _base_entry_version: number; }; + publish_details: Record[]; +} & AnyProperty; + +type PublishDetails = { + environment: string; + locale: string; + time: string; + user: string; + version: number; } & AnyProperty; export type EntryDataForVariantEntries = { @@ -20,7 +29,6 @@ export type EntryDataForVariantEntries = { }; export type CreateVariantEntryDto = { - title: string; _variant: { _change_set: string[]; }; @@ -31,4 +39,23 @@ export type CreateVariantEntryOptions = { entry_uid: string; variant_id: string; content_type_uid: string; -}; \ No newline at end of file +}; + +export type PublishVariantEntryOptions = { + entry_uid: string; + content_type_uid: string; +}; + +export type PublishVariantEntryDto = { + entry: { + environments: string[]; + locales: string[]; + publish_with_base_entry: boolean; + variants: { + uid: string; + version?: number; + }[]; + } + locale: string; + version?: number; +} & AnyProperty; \ No newline at end of file diff --git a/packages/contentstack-variants/src/utils/variant-api-adapter.ts b/packages/contentstack-variants/src/utils/variant-api-adapter.ts index b4ca5e45dd..aeb1f73f22 100644 --- a/packages/contentstack-variants/src/utils/variant-api-adapter.ts +++ b/packages/contentstack-variants/src/utils/variant-api-adapter.ts @@ -22,6 +22,8 @@ import { CreateVariantEntryDto, CreateVariantEntryOptions, APIResponse, + PublishVariantEntryDto, + PublishVariantEntryOptions, } from '../types'; import messages from '../messages'; import { AdapterHelper } from './adapter-helper'; @@ -64,10 +66,12 @@ export class VariantHttpClient extends AdapterHelper implement getAllData, returnResult, content_type_uid, + locale, skip = variantConfig.query.skip || 0, limit = variantConfig.query.limit || 100, - locale = variantConfig.query.locale || 'en-us', include_variant = variantConfig.query.include_variant || true, + include_count = variantConfig.query.include_count || true, + include_publish_details = variantConfig.query.include_publish_details || true, } = options; if (variantConfig.serveMockData && callback) { @@ -79,6 +83,7 @@ export class VariantHttpClient extends AdapterHelper implement callback(data); return; } + if (!locale) return; const start = Date.now(); let endpoint = `/content_types/${content_type_uid}/entries/${entry_uid}/variants?locale=${locale}`; @@ -95,13 +100,29 @@ export class VariantHttpClient extends AdapterHelper implement endpoint = endpoint.concat(`&include_variant=${include_variant}`); } - const query = this.constructQuery(omit(variantConfig.query, ['skip', 'limit', 'locale', 'include_variant'])); + if (include_count) { + endpoint = endpoint.concat(`&include_count=${include_count}`); + } + + if (include_publish_details) { + endpoint = endpoint.concat(`&include_publish_details=${include_publish_details}`); + } + + const query = this.constructQuery( + omit(variantConfig.query, [ + 'skip', + 'limit', + 'locale', + 'include_variant', + 'include_count', + 'include_publish_details', + ]), + ); if (query) { endpoint = endpoint.concat(query); } - // FIXME once API is ready, validate and adjust the response accordingly const data = await this.apiClient.get(endpoint); const response = this.handleVariantAPIRes(data) as { entries: VariantEntryStruct[]; count: number }; @@ -154,12 +175,11 @@ export class VariantHttpClient extends AdapterHelper implement endpoint = endpoint.concat(query); } - const onSuccess = (response: any) => - resolve({ response, apiData: { variantUid, entryUid: entry_uid, title: input.title }, log }); + const onSuccess = (response: any) => resolve({ response, apiData: { variantUid, entryUid: entry_uid }, log }); const onReject = (error: any) => reject({ error, - apiData: { variantUid, entryUid: entry_uid, title: input.title }, + apiData: { variantUid, entryUid: entry_uid }, log, }); @@ -177,6 +197,47 @@ export class VariantHttpClient extends AdapterHelper implement } } + /** + * Publishes a variant entry. + * + * @param input - The input data for publishing the variant entry. + * @param options - The options for publishing the variant entry. + * @param apiParams - Additional API parameters. + * @returns A Promise that resolves to the published variant entry response. + */ + async publishVariantEntry( + input: PublishVariantEntryDto, + options: PublishVariantEntryOptions, + apiParams: Record, + ) { + const { reject, resolve, log } = apiParams; + const { entry_uid, content_type_uid } = options; + let endpoint = `content_types/${content_type_uid}/entries/${entry_uid}/publish`; + + const onSuccess = (response: any) => + resolve({ response, apiData: { entryUid: entry_uid, variantUid: input.entry.variants[0].uid }, log }); + const onReject = (error: any) => + reject({ + error, + apiData: { entryUid: entry_uid, variantUid: input.entry.variants[0].uid }, + log, + }); + + try { + this.apiClient.headers({ api_version: 3.2 }); + const res = await this.apiClient.post(endpoint, input); + const data = this.handleVariantAPIRes(res); + + if (res.status >= 200 && res.status < 300) { + onSuccess(data); + } else { + onReject(data); + } + } catch (error: any) { + onReject(error); + } + } + /** * Handles the API response for variant requests. * @param res - The API response object.