Skip to content

Commit

Permalink
Merge pull request #36 from contentstack/feat/DX-319-variant-entries-…
Browse files Browse the repository at this point in the history
…publish

feat: implemented entry variants publish
  • Loading branch information
aman19K authored May 23, 2024
2 parents db33448 + 6680f8c commit 6708e7a
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 46 deletions.
3 changes: 2 additions & 1 deletion packages/contentstack-export/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
Expand Down
6 changes: 4 additions & 2 deletions packages/contentstack-export/src/export/modules/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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');
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/contentstack-export/src/types/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
29 changes: 19 additions & 10 deletions packages/contentstack-variants/src/export/variant-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,33 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
});

const callback = (variantEntries: Record<string, any>[]) => {
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');
}
}
}
}
151 changes: 128 additions & 23 deletions packages/contentstack-variants/src/import/variant-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,6 +20,7 @@ import {
ImportHelperMethodsConfig,
EntryDataForVariantEntries,
CreateVariantEntryDto,
PublishVariantEntryDto,
} from '../types';
import { fsUtil } from '../utils';

Expand All @@ -33,8 +36,8 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
public assetUidMapper!: Record<string, any>;
public entriesUidMapper!: Record<string, any>;
private installedExtensions!: Record<string, any>[];
private variantUidMapper: Record<string, string>;
private variantUidMapperPath!: string;
private environments!: Record<string, any>;

constructor(
readonly config: ImportConfig & { helpers?: ImportHelperMethodsConfig },
Expand All @@ -57,8 +60,6 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
this.entriesMapperPath = resolve(config.backupDir, config.branchName || '', 'mapper', 'entries');
this.personalizationConfig = this.config.modules.personalization;
this.entriesDirPath = resolve(config.backupDir, config.branchName || '', config.modules.entries.dirName);
this.variantUidMapperPath = resolve(this.entriesMapperPath, 'variant-uid-mapping.json');
this.variantUidMapper = {};
}

/**
Expand Down Expand Up @@ -107,7 +108,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
'success.json',
);
const marketplaceAppMapperPath = resolve(this.config.backupDir, 'mapper', 'marketplace_apps', 'uid-mapping.json');
const envPath = resolve(this.config.backupDir, 'mapper', 'environments', 'environments.json');
const envPath = resolve(this.config.backupDir, 'environments', 'environments.json');
// NOTE Read and store list of variant IDs
this.variantIdList = (fsUtil.readFile(variantIdPath, true) || {}) as Record<string, unknown>;
if (isEmpty(this.variantIdList)) {
Expand All @@ -122,10 +123,12 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
this.taxonomies = (fsUtil.readFile(taxonomiesPath, true) || {}) as Record<string, unknown>;
this.assetUidMapper = (fsUtil.readFile(assetUidMapperPath, true) || {}) as Record<string, any>;
this.assetUrlMapper = (fsUtil.readFile(assetUrlMapperPath, true) || {}) as Record<string, any>;
this.environments = (fsUtil.readFile(envPath, true) || {}) as Record<string, any>;

for (const entriesForVariant of entriesForVariants) {
await this.importVariantEntries(entriesForVariant);
}
this.log(this.config, 'All the variant-entries have been imported & published successfully', 'success');
}

/**
Expand All @@ -147,7 +150,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
try {
const variantEntries = (await fs.readChunkFiles.next()) as VariantEntryStruct[];

await this.handleCuncurrency(contentType, variantEntries, entriesForVariant);
await this.handleConcurrency(contentType, variantEntries, entriesForVariant);
} catch (error) {
this.log(this.config, error, 'error');
}
Expand All @@ -168,7 +171,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
* `Promise.allSettled`. The function also includes logic to handle variant IDs and delays between
* batch processing.
*/
async handleCuncurrency(
async handleConcurrency(
contentType: ContentTypeStruct,
variantEntries: VariantEntryStruct[],
entriesForVariant: EntryDataForVariantEntries,
Expand All @@ -187,23 +190,23 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
const start = Date.now();

for (let [, variantEntry] of entries(batch)) {
const onSuccess = ({ response, apiData: { entryUid, variantUid, title }, log }: any) => {
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(
Expand All @@ -217,7 +220,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
{
reject: onReject.bind(this),
resolve: onSuccess.bind(this),
variantUid,
variantUid: variant_id,
log: this.log,
},
);
Expand All @@ -229,18 +232,32 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
}

// NOTE Handle the API response here
const resultSet = await Promise.allSettled(allPromise);
fsUtil.writeFile(this.variantUidMapperPath, this.variantUidMapper);
this.log(this.config, `Batch No. (${batchNo}/${batches.length}) of variant entry import is complete`, 'success');

await Promise.allSettled(allPromise);
// NOTE publish all the entries
this.publishVariantEntries(resultSet);
await this.publishVariantEntries(batch, entryUid, content_type);
const end = Date.now();
const exeTime = end - start;
this.variantInstance.delay(1000 - exeTime);
}
}

/**
* Serializes the change set of a variant entry.
* @param variantEntry - The variant entry to serialize.
* @returns The serialized change set as a record.
*/
serializeChangeSet(variantEntry: VariantEntryStruct) {
let changeSet: Record<string, any> = {};
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.
Expand Down Expand Up @@ -309,9 +326,97 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
return variantEntry;
}

publishVariantEntries(resultSet: PromiseSettledResult<unknown>[]) {
// 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<string>;
locales: Array<string>;
} {
const requestObject: {
environments: Array<string>;
locales: Array<string>;
} = {
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;
}
}
3 changes: 2 additions & 1 deletion packages/contentstack-variants/src/types/export-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>[]) => void;
} & AnyProperty;

Expand Down
Loading

0 comments on commit 6708e7a

Please sign in to comment.