Skip to content

Commit

Permalink
Merge pull request #41 from contentstack/fix/DX-643/DX-644
Browse files Browse the repository at this point in the history
fixed entry variant import for file fields and fixed project name dup…
  • Loading branch information
shafeeqd959 authored Jun 6, 2024
2 parents c8e800f + 284691f commit 314402a
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 69 deletions.
4 changes: 4 additions & 0 deletions packages/contentstack-import/src/commands/cm/stacks/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ export default class ImportCommand extends Command {
default: false,
description: 'Skips the module exists warning messages.',
}),
'personalize-project-name': flags.string({
required: false,
description: 'Personalize project name.',
}),
'skip-audit': flags.boolean({
description: 'Skips the audit fix.',
}),
Expand Down
1 change: 1 addition & 0 deletions packages/contentstack-import/src/types/import-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default interface ImportConfig extends DefaultConfig, ExternalConfig {
skipAudit?: boolean;
stackName?: string;
region: Region;
personalizeProjectName?: string;
}

type branch = {
Expand Down
34 changes: 17 additions & 17 deletions packages/contentstack-import/src/utils/asset-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,24 +373,24 @@ function findFileUrls(schema: any, _entry: any, assetUrls: any) {
}

function updateFileFields(
objekt: any,
object: any,
parent: any,
pos: any,
mappedAssetUids: any,
matchedUids: any,
unmatchedUids: any,
mappedAssetUrls?: any,
) {
if (_.isPlainObject(objekt) && _.has(objekt, 'filename') && _.has(objekt, 'uid')) {
if (_.isPlainObject(object) && _.has(object, 'filename') && _.has(object, 'uid')) {
if (typeof pos !== 'undefined') {
if (typeof pos === 'number' || typeof pos === 'string') {
const replacer = () => {
if (mappedAssetUids.hasOwnProperty(objekt.uid)) {
parent[pos] = mappedAssetUids[objekt.uid];
matchedUids.push(objekt.uid);
if (mappedAssetUids.hasOwnProperty(object.uid)) {
parent[pos] = mappedAssetUids[object.uid];
matchedUids.push(object.uid);
} else {
parent[pos] = '';
unmatchedUids.push(objekt.uid);
unmatchedUids.push(object.uid);
}
};

Expand All @@ -399,7 +399,7 @@ function updateFileFields(
}

if (
objekt &&
object &&
_.isObject(parent[pos]) &&
parent[pos].uid &&
parent[pos].url &&
Expand All @@ -415,23 +415,23 @@ function updateFileFields(
parent = _.omit(parent, ['asset']);
}

if (objekt.uid && mappedAssetUids && mappedAssetUids[objekt.uid]) {
objekt.uid = mappedAssetUids[objekt.uid];
if (object.uid && mappedAssetUids && mappedAssetUids[object.uid]) {
object.uid = mappedAssetUids[object.uid];
}
if (objekt.url && mappedAssetUrls && mappedAssetUrls[objekt.url]) {
objekt.url = mappedAssetUrls[objekt.url];
if (object.url && mappedAssetUrls && mappedAssetUrls[object.url]) {
object.url = mappedAssetUrls[object.url];
}
} else {
replacer();
}
}
}
} else if (_.isPlainObject(objekt)) {
for (let key in objekt) updateFileFields(objekt[key], objekt, key, mappedAssetUids, matchedUids, unmatchedUids);
} else if (_.isArray(objekt) && objekt.length) {
for (let i = 0; i <= objekt.length; i++)
updateFileFields(objekt[i], objekt, i, mappedAssetUids, matchedUids, unmatchedUids);
} else if (_.isPlainObject(object)) {
for (let key in object) updateFileFields(object[key], object, key, mappedAssetUids, matchedUids, unmatchedUids);
} else if (_.isArray(object) && object.length) {
for (let i = 0; i <= object.length; i++)
updateFileFields(object[i], object, i, mappedAssetUids, matchedUids, unmatchedUids);

parent[pos] = _.compact(objekt);
parent[pos] = _.compact(object);
}
}
11 changes: 5 additions & 6 deletions packages/contentstack-import/src/utils/import-config-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,9 @@ const setupConfig = async (importCmdFlags: any): Promise<ImportConfig> => {
config.contentDir = importCmdFlags['data'] || importCmdFlags['data-dir'] || config.data || (await askContentDir());
const pattern = /[*$%#<>{}!&?]/g;
if (pattern.test(config.contentDir)) {
cliux.print(
`\nPlease add a directory path without any of the special characters: (*,&,{,},[,],$,%,<,>,?,!)`,
{
color: 'yellow',
},
);
cliux.print(`\nPlease add a directory path without any of the special characters: (*,&,{,},[,],$,%,<,>,?,!)`, {
color: 'yellow',
});
config.contentDir = await askContentDir();
}
config.contentDir = config.contentDir.replace(/['"]/g, '');
Expand Down Expand Up @@ -99,6 +96,8 @@ const setupConfig = async (importCmdFlags: any): Promise<ImportConfig> => {
config.replaceExisting = importCmdFlags['replace-existing'];
config.skipExisting = importCmdFlags['skip-existing'];

config.personalizeProjectName = importCmdFlags['personalize-project-name'];

return config;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
entry_uid: entry.uid,
locale,
});
if (existsSync(variantEntryBasePath)){
if (existsSync(variantEntryBasePath)) {
variantEntriesFs.completeFile(true);
this.log(
this.config,
Expand Down
49 changes: 28 additions & 21 deletions packages/contentstack-variants/src/import/project.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';

import { PersonalizationAdapter } from '../utils';
import { PersonalizationAdapter, askProjectName } from '../utils';
import { APIConfig, CreateProjectInput, ImportConfig, LogType } from '../types';

export default class Project extends PersonalizationAdapter<ImportConfig> {
Expand All @@ -24,29 +24,36 @@ export default class Project extends PersonalizationAdapter<ImportConfig> {
const projectPath = join(this.config.data, personalization.dirName, dirName, fileName);

if (existsSync(projectPath)) {
try {
const projects = JSON.parse(readFileSync(projectPath, 'utf8')) as CreateProjectInput[];

if (!projects || projects.length < 1) {
this.config.modules.personalization.importData = false; // Stop personalization import if stack not connected to any project
this.log(this.config, 'No project found!', 'info');
return;
}

for (const project of projects) {
const { name, description } = project;
const projectRes = await this.createProject({
name: `Copy of ${name}`,
description,
const projects = JSON.parse(readFileSync(projectPath, 'utf8')) as CreateProjectInput[];

if (!projects || projects.length < 1) {
this.config.modules.personalization.importData = false; // Stop personalization import if stack not connected to any project
this.log(this.config, 'No project found!', 'info');
return;
}

for (const project of projects) {
let projectRes: any;
const createProject = async (newName: void | string) => {
projectRes = await this.createProject({
name: newName || project.name,
description: project.description,
connectedStackApiKey: this.config.apiKey,
}).catch(async (error) => {
if (error === 'personalization.PROJECTS.DUPLICATE_NAME') {
const projectName = await askProjectName('Copy Of ' + (newName || project.name));
return await createProject(projectName);
}
this.log(this.config, this.$t(this.messages.CREATE_FAILURE, { module: 'Projects' }), 'error');
throw error;
});
this.config.modules.personalization.project_id = projectRes?.uid;
}
};

await createProject(this.config.personalizeProjectName);

this.config.modules.personalization.project_id = projectRes.uid;
this.config.modules.personalization.importData = true;
this.log(this.config, this.$t(this.messages.CREATE_SUCCESS, { module: 'Projects' }), 'info');
} catch (error) {
this.log(this.config, this.$t(this.messages.CREATE_FAILURE, { module: 'Projects' }), 'error');
throw error;
this.log(this.config, `Project Created Successfully: ${projectRes.uid}`, 'info');
}
} else {
this.config.modules.personalization.importData = false; // Stop personalization import if stack not connected to any project
Expand Down
47 changes: 42 additions & 5 deletions packages/contentstack-variants/src/import/variant-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
CreateVariantEntryDto,
PublishVariantEntryDto,
} from '../types';
import { fsUtil, log } from '../utils';
import { formatError, fsUtil, log } from '../utils';

export default class VariantEntries extends VariantAdapter<VariantHttpClient<ImportConfig>> {
public entriesDirPath: string;
Expand Down Expand Up @@ -254,9 +254,10 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
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];
variantEntry._variant._change_set.forEach((key: string) => {
key = key.split('.')[0];
if (variantEntry[key]) {
changeSet[key] = variantEntry[key];
}
});
}
Expand Down Expand Up @@ -315,6 +316,9 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
// if not, feel free to remove this lookup flow.
lookUpTerms(contentType.schema, variantEntry, this.taxonomies, this.config);

// update file fields of entry variants to support lookup asset logic
this.updateFileFields(variantEntry);

// NOTE Find and replace asset's UID
variantEntry = lookupAssets(
{
Expand All @@ -331,6 +335,39 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
return variantEntry;
}

/**
* Updates the file fields of a entry variant to support lookup asset logic.
* Lookup asset expects file fields to be an object instead of a string. So here we are updating the file fields to be an object. Object has two keys: `uid` and `filename`. `uid` is the asset UID and `filename` is the name of the file. Used a dummy value for the filename. This is a temporary fix and will be updated in the future.
* @param variantEntry - The entry variant to update.
*/
updateFileFields(variantEntry: VariantEntryStruct) {
const setValue = (currentObj: VariantEntryStruct, keys: Array<string>) => {
if (!currentObj || keys.length === 0) return;

const [firstKey, ...restKeys] = keys;

if (Array.isArray(currentObj)) {
for (const item of currentObj) {
setValue(item, [firstKey, ...restKeys]);
}
} else if (currentObj && typeof currentObj === 'object') {
if (firstKey in currentObj) {
if (keys.length === 1) {
currentObj[firstKey] = { uid: currentObj[firstKey], filename: 'dummy.jpeg' };
} else {
setValue(currentObj[firstKey], restKeys);
}
}
}
};

const pathsToUpdate = variantEntry._metadata.references
.filter((ref: any) => ref._content_type_uid === 'sys_assets')
.map((ref: any) => ref.path);

pathsToUpdate.forEach((path: string) => setValue(variantEntry, path.split('.')));
}

/**
* 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.
Expand Down Expand Up @@ -373,7 +410,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
};
const onReject = ({ error, apiData: { entryUid, variantUid }, log }: any) => {
log(this.config, `Failed to publish entry variant: '${variantUid}' of entry uid ${entryUid}`, 'error');
log(this.config, error, 'error');
log(this.config, formatError(error), 'error');
};

const { environments, locales } = this.serializePublishEntries(variantEntry);
Expand Down
47 changes: 28 additions & 19 deletions packages/contentstack-variants/src/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { FsUtility } from '@contentstack/cli-utilities';
import { FsUtility, cliux } from '@contentstack/cli-utilities';

export const formatError = function (error: any) {
try {
if (typeof error === 'string') {
error = JSON.parse(error);
} else {
error = JSON.parse(error.message);
}
} catch (e) {}
let message = error.errorMessage || error.error_message || error.message || error;
if (error.errors && Object.keys(error.errors).length > 0) {
Object.keys(error.errors).forEach((e) => {
let entity = e;
if (e === 'authorization') entity = 'Management Token';
if (e === 'api_key') entity = 'Stack API key';
if (e === 'uid') entity = 'Content Type';
if (e === 'access_token') entity = 'Delivery Token';
message += ' ' + [entity, error.errors[e]].join(' ');
});
try {
if (typeof error === 'string') {
error = JSON.parse(error);
} else {
error = JSON.parse(error.message);
}
return message;
} catch (e) {}
let message = error.errorMessage || error.error_message || error.message || error;
if (error.errors && Object.keys(error.errors).length > 0) {
Object.keys(error.errors).forEach((e) => {
let entity = e;
if (e === 'authorization') entity = 'Management Token';
if (e === 'api_key') entity = 'Stack API key';
if (e === 'uid') entity = 'Content Type';
if (e === 'access_token') entity = 'Delivery Token';
message += ' ' + [entity, error.errors[e]].join(' ');
});
}
return message;
};

export const fsUtil = new FsUtility();

export const askProjectName = async (defaultValue: unknown): Promise<string> => {
return await cliux.inquire({
type: 'input',
name: 'name',
default: defaultValue,
message: 'Enter the project name:',
});
};

0 comments on commit 314402a

Please sign in to comment.