Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 80 additions & 46 deletions packages/contentstack-export-to-csv/src/commands/cm/export-to-csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const {
cliux,
doesBranchExist,
isManagementTokenValid,
log
} = require('@contentstack/cli-utilities');
const util = require('../../util');
const config = require('../../util/config');
Expand All @@ -18,7 +17,8 @@ class ExportToCsvCommand extends Command {
required: false,
multiple: false,
options: ['entries', 'users', 'teams', 'taxonomies'],
description: 'Option to export data (entries, users, teams, taxonomies). <options: entries|users|teams|taxonomies>',
description:
'Option to export data (entries, users, teams, taxonomies). <options: entries|users|teams|taxonomies>',
}),
alias: flags.string({
char: 'a',
Expand Down Expand Up @@ -67,12 +67,22 @@ class ExportToCsvCommand extends Command {
'taxonomy-uid': flags.string({
description: 'Provide the taxonomy UID of the related terms you want to export.',
}),
'include-fallback': flags.boolean({
description:
"[Optional] Include fallback locale data when exporting taxonomies. When enabled, if a taxonomy term doesn't exist in the specified locale, it will fallback to the hierarchy defined in the branch settings.",
default: false,
}),
'fallback-locale': flags.string({
description:
"[Optional] Specify a specific fallback locale for taxonomy export. This locale will be used when a taxonomy term doesn't exist in the primary locale. Takes priority over branch fallback hierarchy when both are specified.",
required: false,
}),
delimiter: flags.string({
description: '[optional] Provide a delimiter to separate individual data fields within the CSV file. For example: cm:export-to-csv --delimiter \'|\'',
description:
"[optional] Provide a delimiter to separate individual data fields within the CSV file. For example: cm:export-to-csv --delimiter '|'",
default: ',',
}),
};

};
async run() {
try {
let action, managementAPIClient;
Expand All @@ -87,9 +97,11 @@ class ExportToCsvCommand extends Command {
'content-type': contentTypesFlag,
alias: managementTokenAlias,
branch: branchUid,
"team-uid": teamUid,
'team-uid': teamUid,
'taxonomy-uid': taxonomyUID,
delimiter
'include-fallback': includeFallback,
'fallback-locale': fallbackLocale,
delimiter,
},
} = await this.parse(ExportToCsvCommand);

Expand Down Expand Up @@ -127,7 +139,12 @@ class ExportToCsvCommand extends Command {
}

stackAPIClient = this.getStackClient(managementAPIClient, stack);
stackAPIClient = await this.checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient);
stackAPIClient = await this.checkAndUpdateBranchDetail(
branchUid,
stack,
stackAPIClient,
managementAPIClient,
);

const contentTypeCount = await util.getContentTypeCount(stackAPIClient);

Expand Down Expand Up @@ -223,15 +240,15 @@ class ExportToCsvCommand extends Command {
}
case config.exportTeams:
case 'teams': {
try{
try {
let organization;
if (org) {
organization = { uid: org, name: orgName || org };
} else {
organization = await util.chooseOrganization(managementAPIClient, action); // prompt for organization
}
await util.exportTeams(managementAPIClient,organization,teamUid, delimiter);

await util.exportTeams(managementAPIClient, organization, teamUid, delimiter);
} catch (error) {
if (error.message || error.errorMessage) {
cliux.error(util.formatError(error));
Expand All @@ -242,7 +259,11 @@ class ExportToCsvCommand extends Command {
case config.exportTaxonomies:
case 'taxonomies': {
let stack;
let language;
let stackAPIClient;
let finalIncludeFallback = includeFallback;
let finalFallbackLocale = fallbackLocale;

if (managementTokenAlias) {
const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName);
managementAPIClient = apiClient;
Expand All @@ -252,7 +273,29 @@ class ExportToCsvCommand extends Command {
}

stackAPIClient = this.getStackClient(managementAPIClient, stack);
await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID, delimiter);
if (locale) {
language = { code: locale };
} else {
language = await util.chooseLanguage(stackAPIClient);
}

if (includeFallback === undefined || fallbackLocale === undefined) {
const fallbackOptions = await util.chooseFallbackOptions(stackAPIClient);

if (includeFallback === undefined) {
finalIncludeFallback = fallbackOptions.includeFallback;
}
if (fallbackLocale === undefined && fallbackOptions.fallbackLocale) {
finalFallbackLocale = fallbackOptions.fallbackLocale;
}
}

await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID, delimiter, {
locale: language.code,
branch: branchUid,
include_fallback: finalIncludeFallback,
fallback_locale: finalFallbackLocale,
});
break;
}
}
Expand Down Expand Up @@ -287,7 +330,7 @@ class ExportToCsvCommand extends Command {
.query()
.find()
.then(({ items }) => (items !== undefined ? items : []))
.catch((_err) => {});
.catch(() => {});
}

/**
Expand Down Expand Up @@ -335,9 +378,14 @@ class ExportToCsvCommand extends Command {
let apiClient, stackDetails;
const listOfTokens = configHandler.get('tokens');
if (managementTokenAlias && listOfTokens[managementTokenAlias]) {
const checkManagementTokenValidity = await isManagementTokenValid((listOfTokens[managementTokenAlias].apiKey) ,listOfTokens[managementTokenAlias].token);
if(checkManagementTokenValidity.hasOwnProperty('message')) {
throw checkManagementTokenValidity.valid==='failedToCheck'?checkManagementTokenValidity.message:(`error: Management token or stack API key is invalid. ${checkManagementTokenValidity.message}`);
const checkManagementTokenValidity = await isManagementTokenValid(
listOfTokens[managementTokenAlias].apiKey,
listOfTokens[managementTokenAlias].token,
);
if (Object.prototype.hasOwnProperty.call(checkManagementTokenValidity, 'message')) {
throw checkManagementTokenValidity.valid === 'failedToCheck'
? checkManagementTokenValidity.message
: `error: Management token or stack API key is invalid. ${checkManagementTokenValidity.message}`;
}
apiClient = await managementSDKClient({
host: this.cmaHost,
Expand Down Expand Up @@ -393,13 +441,12 @@ class ExportToCsvCommand extends Command {
* @param {object} stack
* @param {string} taxUID
*/
async createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxUID, delimiter) {
//TODO: Temp variable to export taxonomies in importable format will replaced with flag once decided
const importableCSV = true;
async createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxUID, delimiter, localeOptions = {}) {
const payload = {
stackAPIClient,
type: '',
limit: config.limit || 100,
...localeOptions, // Spread locale, branch, include_fallback, fallback_locale
};
//check whether the taxonomy is valid or not
let taxonomies = [];
Expand All @@ -410,37 +457,14 @@ class ExportToCsvCommand extends Command {
} else {
taxonomies = await util.getAllTaxonomies(payload);
}

if (!importableCSV) {
const formattedTaxonomiesData = util.formatTaxonomiesData(taxonomies);
if (formattedTaxonomiesData?.length) {
const fileName = `${stackName ? stackName : stack.name}_taxonomies.csv`;
util.write(this, formattedTaxonomiesData, fileName, 'taxonomies', delimiter);
} else {
cliux.print('info: No taxonomies found! Please provide a valid stack.', { color: 'blue' });
}

for (let index = 0; index < taxonomies?.length; index++) {
const taxonomy = taxonomies[index];
const taxonomyUID = taxonomy?.uid;
if (taxonomyUID) {
payload['taxonomyUID'] = taxonomyUID;
const terms = await util.getAllTermsOfTaxonomy(payload);
const formattedTermsData = util.formatTermsOfTaxonomyData(terms, taxonomyUID);
const taxonomyName = taxonomy?.name ?? '';
const termFileName = `${stackName ?? stack.name}_${taxonomyName}_${taxonomyUID}_terms.csv`;
if (formattedTermsData?.length) {
util.write(this, formattedTermsData, termFileName, 'terms', delimiter);
} else {
cliux.print(`info: No terms found for the taxonomy UID - '${taxonomyUID}'!`, { color: 'blue' });
}
}
}

if (!taxonomies?.length) {
cliux.print('info: No taxonomies found!', { color: 'blue' });
} else {
const fileName = `${stackName ?? stack.name}_taxonomies.csv`;
const { taxonomiesData, headers } = await util.createImportableCSV(payload, taxonomies);
if (taxonomiesData?.length) {
util.write(this, taxonomiesData, fileName, 'taxonomies',delimiter, headers);
util.write(this, taxonomiesData, fileName, 'taxonomies', delimiter, headers);
}
}
}
Expand Down Expand Up @@ -486,6 +510,16 @@ ExportToCsvCommand.examples = [
'',
'Exporting taxonomies and respective terms to a .CSV file with a delimiter',
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --delimiter <delimiter>',
'',
'Exporting taxonomies with specific locale',
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --locale <locale>',
'',
'Exporting taxonomies with fallback locale support',
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --locale <locale> --include-fallback',
'',
'Exporting taxonomies with custom fallback locale',
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --locale <locale> --include-fallback --fallback-locale <fallback-locale>',
'',
];

module.exports = ExportToCsvCommand;
85 changes: 71 additions & 14 deletions packages/contentstack-export-to-csv/src/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,50 @@ function startupQuestions() {
});
}

function chooseFallbackOptions(stackAPIClient) {
return new Promise(async (resolve, reject) => {
try {
const questions = [
{
type: 'confirm',
name: 'includeFallback',
message: 'Include fallback locale data when exporting taxonomies?',
default: false,
},
];

const { includeFallback } = await inquirer.prompt(questions);

let fallbackLocale = null;

if (includeFallback) {
// Get available languages for fallback locale selection
const languages = await getLanguages(stackAPIClient);
const languagesList = Object.keys(languages);

const fallbackQuestion = [
{
type: 'list',
name: 'selectedFallbackLocale',
message: 'Choose fallback locale',
choices: languagesList,
},
];

const { selectedFallbackLocale } = await inquirer.prompt(fallbackQuestion);
fallbackLocale = languages[selectedFallbackLocale];
}

resolve({
includeFallback,
fallbackLocale,
});
} catch (error) {
reject(error);
}
});
}

function getOrgUsers(managementAPIClient, orgUid) {
return new Promise((resolve, reject) => {
managementAPIClient
Expand Down Expand Up @@ -1080,11 +1124,17 @@ async function getTaxonomy(payload) {
* @returns {*} Promise<any>
*/
async function taxonomySDKHandler(payload, skip) {
const { stackAPIClient, taxonomyUID, type, format } = payload;
const { stackAPIClient, taxonomyUID, type, format, locale, branch, include_fallback, fallback_locale } = payload;

const queryParams = { include_count: true, limit: payload.limit };
if (skip >= 0) queryParams['skip'] = skip || 0;

// Add locale and branch parameters if provided
if (locale) queryParams['locale'] = locale;
if (branch) queryParams['branch'] = branch;
if (include_fallback !== undefined) queryParams['include_fallback'] = include_fallback;
if (fallback_locale) queryParams['fallback_locale'] = fallback_locale;

switch (type) {
case 'taxonomies':
return await stackAPIClient
Expand All @@ -1109,9 +1159,15 @@ async function taxonomySDKHandler(payload, skip) {
.then((data) => data)
.catch((err) => handleTaxonomyErrorMsg(err));
case 'export-taxonomies':
const exportParams = { format };
if (locale) exportParams.locale = locale;
if (branch) exportParams.branch = branch;
if (include_fallback !== undefined) exportParams.include_fallback = include_fallback;
if (fallback_locale) exportParams.fallback_locale = fallback_locale;

return await stackAPIClient
.taxonomy(taxonomyUID)
.export({ format })
.export(exportParams)
.then((data) => data)
.catch((err) => handleTaxonomyErrorMsg(err));
default:
Expand Down Expand Up @@ -1176,20 +1232,20 @@ function handleTaxonomyErrorMsg(err) {
* @returns
*/
async function createImportableCSV(payload, taxonomies) {
let taxonomiesData = [];
let headers = [];
payload['type'] = 'export-taxonomies';
payload['format'] = 'csv';
for (const taxonomy of taxonomies) {
if (taxonomy?.uid) {
payload['taxonomyUID'] = taxonomy?.uid;
const data = await taxonomySDKHandler(payload);
const taxonomies = await csvParse(data, headers);
taxonomiesData.push(...taxonomies);
}
let taxonomiesData = [];
let headers = [];
payload['type'] = 'export-taxonomies';
payload['format'] = 'csv';
for (const taxonomy of taxonomies) {
if (taxonomy?.uid) {
payload['taxonomyUID'] = taxonomy?.uid;
const data = await taxonomySDKHandler(payload);
const taxonomies = await csvParse(data, headers);
taxonomiesData.push(...taxonomies);
}
}

return { taxonomiesData, headers };
return { taxonomiesData, headers };
}

/**
Expand Down Expand Up @@ -1224,6 +1280,7 @@ module.exports = {
chooseBranch: chooseBranch,
chooseContentType: chooseContentType,
chooseLanguage: chooseLanguage,
chooseFallbackOptions: chooseFallbackOptions,
getEntries: getEntries,
getEnvironments: getEnvironments,
cleanEntries: cleanEntries,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,26 @@
"updated_at": "2023-09-11T10:44:40.213Z",
"ACL": [],
"_version": 1
},
{
"code": "en-us",
"name": "English - United States",
"locale": null,
"uid": "en-us-uid",
"created_at": "2023-09-11T10:44:40.213Z",
"updated_at": "2023-09-11T10:44:40.213Z",
"ACL": [],
"_version": 1
},
{
"code": "fr-fr",
"name": "French - France",
"fallback_locale": "en-us",
"uid": "fr-fr-uid",
"created_at": "2023-09-11T10:44:40.213Z",
"updated_at": "2023-09-11T10:44:40.213Z",
"ACL": [],
"_version": 1
}
],
"Teams": {
Expand Down
Loading