@contentstack/cli-cm-export-to-csv 1.4.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -7
- package/src/commands/cm/export-to-csv.js +240 -71
- package/src/util/config.js +6 -3
- package/src/util/index.js +522 -26
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentstack/cli-cm-export-to-csv",
|
|
3
3
|
"description": "Export entities to csv",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"author": "Abhinav Gupta @abhinav-from-contentstack",
|
|
6
6
|
"bugs": "https://github.com/contentstack/cli/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@contentstack/cli-command": "~1.2.
|
|
9
|
-
"@contentstack/cli-utilities": "~1.5.
|
|
8
|
+
"@contentstack/cli-command": "~1.2.15",
|
|
9
|
+
"@contentstack/cli-utilities": "~1.5.5",
|
|
10
10
|
"chalk": "^4.1.0",
|
|
11
11
|
"fast-csv": "^4.3.6",
|
|
12
12
|
"inquirer": "8.2.4",
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@oclif/test": "^2.2.10",
|
|
18
|
-
"chai": "^4.
|
|
18
|
+
"@types/chai": "^4.3.6",
|
|
19
|
+
"@types/mocha": "^10.0.1",
|
|
20
|
+
"chai": "^4.3.8",
|
|
19
21
|
"debug": "^4.3.1",
|
|
22
|
+
"dotenv": "^16.3.1",
|
|
20
23
|
"eslint": "^7.32.0",
|
|
21
24
|
"eslint-config-oclif": "^4.0.0",
|
|
22
25
|
"globby": "^10.0.2",
|
|
23
|
-
"mocha": "^10.
|
|
26
|
+
"mocha": "^10.2.0",
|
|
24
27
|
"nyc": "^15.1.0",
|
|
25
28
|
"oclif": "^3.8.1"
|
|
26
29
|
},
|
|
@@ -44,7 +47,8 @@
|
|
|
44
47
|
"postpack": "rm -f oclif.manifest.json",
|
|
45
48
|
"prepack": "oclif manifest && oclif readme",
|
|
46
49
|
"test": "nyc mocha --forbid-only \"test/**/*.test.js\"",
|
|
47
|
-
"test:unit": "
|
|
50
|
+
"test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.js\" \"test/util/common-utils.test.js\"",
|
|
51
|
+
"test:unit:report": "nyc --extension .js mocha --forbid-only \"test/unit/**/*.test.js\" \"test/util/common-utils.test.js\"",
|
|
48
52
|
"version": "oclif readme && git add README.md",
|
|
49
53
|
"clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo"
|
|
50
54
|
},
|
|
@@ -61,4 +65,4 @@
|
|
|
61
65
|
}
|
|
62
66
|
},
|
|
63
67
|
"repository": "https://github.com/contentstack/cli"
|
|
64
|
-
}
|
|
68
|
+
}
|
|
@@ -15,8 +15,8 @@ class ExportToCsvCommand extends Command {
|
|
|
15
15
|
action: flags.string({
|
|
16
16
|
required: false,
|
|
17
17
|
multiple: false,
|
|
18
|
-
options: ['entries', 'users'],
|
|
19
|
-
description: `Option to export data (entries, users)`,
|
|
18
|
+
options: ['entries', 'users', 'teams', 'taxonomies'],
|
|
19
|
+
description: `Option to export data (entries, users, teams, taxonomies)`,
|
|
20
20
|
}),
|
|
21
21
|
alias: flags.string({
|
|
22
22
|
char: 'a',
|
|
@@ -59,6 +59,16 @@ class ExportToCsvCommand extends Command {
|
|
|
59
59
|
multiple: false,
|
|
60
60
|
required: false,
|
|
61
61
|
}),
|
|
62
|
+
"team-uid": flags.string({
|
|
63
|
+
description: 'Uid of the team whose user data and stack roles are required'
|
|
64
|
+
}),
|
|
65
|
+
'taxonomy-uid': flags.string({
|
|
66
|
+
description: 'Provide the taxonomy UID of the related terms you want to export',
|
|
67
|
+
}),
|
|
68
|
+
delimiter: flags.string({
|
|
69
|
+
description: '[optional] Provide a delimiter to separate individual data fields within the CSV file.',
|
|
70
|
+
default: ',',
|
|
71
|
+
}),
|
|
62
72
|
};
|
|
63
73
|
|
|
64
74
|
async run() {
|
|
@@ -75,11 +85,20 @@ class ExportToCsvCommand extends Command {
|
|
|
75
85
|
'content-type': contentTypesFlag,
|
|
76
86
|
alias: managementTokenAlias,
|
|
77
87
|
branch: branchUid,
|
|
88
|
+
"team-uid": teamUid,
|
|
89
|
+
'taxonomy-uid': taxonomyUID,
|
|
90
|
+
delimiter
|
|
78
91
|
},
|
|
79
92
|
} = await this.parse(ExportToCsvCommand);
|
|
80
93
|
|
|
81
94
|
if (!managementTokenAlias) {
|
|
82
95
|
managementAPIClient = await managementSDKClient({ host: this.cmaHost });
|
|
96
|
+
if (!isAuthenticated()) {
|
|
97
|
+
this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, {
|
|
98
|
+
exit: 2,
|
|
99
|
+
suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
if (actionFlag) {
|
|
@@ -96,69 +115,17 @@ class ExportToCsvCommand extends Command {
|
|
|
96
115
|
let stackAPIClient;
|
|
97
116
|
let language;
|
|
98
117
|
let contentTypes = [];
|
|
99
|
-
let stackBranches;
|
|
100
|
-
const listOfTokens = configHandler.get('tokens');
|
|
101
|
-
|
|
102
|
-
if (managementTokenAlias && listOfTokens[managementTokenAlias]) {
|
|
103
|
-
managementAPIClient = await managementSDKClient({
|
|
104
|
-
host: this.cmaHost,
|
|
105
|
-
management_token: listOfTokens[managementTokenAlias].token,
|
|
106
|
-
});
|
|
107
|
-
stack = {
|
|
108
|
-
name: stackName || managementTokenAlias,
|
|
109
|
-
apiKey: listOfTokens[managementTokenAlias].apiKey,
|
|
110
|
-
token: listOfTokens[managementTokenAlias].token,
|
|
111
|
-
};
|
|
112
|
-
} else if (managementTokenAlias) {
|
|
113
|
-
this.error('Provided management token alias not found in your config.!');
|
|
114
|
-
} else {
|
|
115
|
-
let organization;
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (org) {
|
|
125
|
-
organization = { uid: org };
|
|
126
|
-
} else {
|
|
127
|
-
organization = await util.chooseOrganization(managementAPIClient); // prompt for organization
|
|
128
|
-
}
|
|
129
|
-
if (!stackAPIKey) {
|
|
130
|
-
stack = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack
|
|
131
|
-
} else {
|
|
132
|
-
stack = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey);
|
|
133
|
-
}
|
|
119
|
+
if (managementTokenAlias) {
|
|
120
|
+
const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName);
|
|
121
|
+
managementAPIClient = apiClient;
|
|
122
|
+
stack = stackDetails;
|
|
123
|
+
} else {
|
|
124
|
+
stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org);
|
|
134
125
|
}
|
|
135
126
|
|
|
136
127
|
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
137
|
-
|
|
138
|
-
if (branchUid) {
|
|
139
|
-
try {
|
|
140
|
-
const branchExists = await doesBranchExist(stackAPIClient, branchUid);
|
|
141
|
-
if (branchExists?.errorCode) {
|
|
142
|
-
throw new Error(branchExists.errorMessage);
|
|
143
|
-
}
|
|
144
|
-
stack.branch_uid = branchUid;
|
|
145
|
-
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
if (error.message || error.errorMessage) {
|
|
148
|
-
cliux.error(util.formatError(error));
|
|
149
|
-
this.exit();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} else {
|
|
153
|
-
stackBranches = await this.getStackBranches(stackAPIClient);
|
|
154
|
-
if (stackBranches === undefined) {
|
|
155
|
-
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
156
|
-
} else {
|
|
157
|
-
const { branch } = await util.chooseBranch(stackBranches);
|
|
158
|
-
stack.branch_uid = branch;
|
|
159
|
-
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
128
|
+
await this.checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient);
|
|
162
129
|
|
|
163
130
|
const contentTypeCount = await util.getContentTypeCount(stackAPIClient);
|
|
164
131
|
|
|
@@ -217,7 +184,7 @@ class ExportToCsvCommand extends Command {
|
|
|
217
184
|
flatEntries = flatEntries.concat(flatEntriesResult);
|
|
218
185
|
}
|
|
219
186
|
let fileName = `${stackName ? stackName : stack.name}_${contentType}_${language.code}_entries_export.csv`;
|
|
220
|
-
util.write(this, flatEntries, fileName, 'entries'); // write to file
|
|
187
|
+
util.write(this, flatEntries, fileName, 'entries', delimiter); // write to file
|
|
221
188
|
}
|
|
222
189
|
} catch (error) {
|
|
223
190
|
cliux.error(util.formatError(error));
|
|
@@ -227,12 +194,6 @@ class ExportToCsvCommand extends Command {
|
|
|
227
194
|
case config.exportUsers:
|
|
228
195
|
case 'users': {
|
|
229
196
|
try {
|
|
230
|
-
if (!isAuthenticated()) {
|
|
231
|
-
this.error(config.CLI_EXPORT_CSV_LOGIN_FAILED, {
|
|
232
|
-
exit: 2,
|
|
233
|
-
suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'],
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
197
|
let organization;
|
|
237
198
|
|
|
238
199
|
if (org) {
|
|
@@ -250,7 +211,25 @@ class ExportToCsvCommand extends Command {
|
|
|
250
211
|
(orgName ? orgName : organization.name).replace(config.organizationNameRegex, ''),
|
|
251
212
|
)}_users_export.csv`;
|
|
252
213
|
|
|
253
|
-
util.write(this, listOfUsers, fileName, 'organization details');
|
|
214
|
+
util.write(this, listOfUsers, fileName, 'organization details', delimiter);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error.message || error.errorMessage) {
|
|
217
|
+
cliux.error(util.formatError(error));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case config.exportTeams:
|
|
223
|
+
case 'teams': {
|
|
224
|
+
try{
|
|
225
|
+
let organization;
|
|
226
|
+
if (org) {
|
|
227
|
+
organization = { uid: org, name: orgName || org };
|
|
228
|
+
} else {
|
|
229
|
+
organization = await util.chooseOrganization(managementAPIClient, action); // prompt for organization
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await util.exportTeams(managementAPIClient,organization,teamUid, delimiter);
|
|
254
233
|
} catch (error) {
|
|
255
234
|
if (error.message || error.errorMessage) {
|
|
256
235
|
cliux.error(util.formatError(error));
|
|
@@ -258,6 +237,22 @@ class ExportToCsvCommand extends Command {
|
|
|
258
237
|
}
|
|
259
238
|
break;
|
|
260
239
|
}
|
|
240
|
+
case config.exportTaxonomies:
|
|
241
|
+
case 'taxonomies': {
|
|
242
|
+
let stack;
|
|
243
|
+
let stackAPIClient;
|
|
244
|
+
if (managementTokenAlias) {
|
|
245
|
+
const { stackDetails, apiClient } = await this.getAliasDetails(managementTokenAlias, stackName);
|
|
246
|
+
managementAPIClient = apiClient;
|
|
247
|
+
stack = stackDetails;
|
|
248
|
+
} else {
|
|
249
|
+
stack = await this.getStackDetails(managementAPIClient, stackAPIKey, org);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
253
|
+
await this.createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxonomyUID, delimiter);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
261
256
|
}
|
|
262
257
|
} catch (error) {
|
|
263
258
|
if (error.message || error.errorMessage) {
|
|
@@ -273,8 +268,8 @@ class ExportToCsvCommand extends Command {
|
|
|
273
268
|
getStackClient(managementAPIClient, stack) {
|
|
274
269
|
const stackInit = {
|
|
275
270
|
api_key: stack.apiKey,
|
|
276
|
-
branch_uid: stack.branch_uid,
|
|
277
271
|
};
|
|
272
|
+
if (stack?.branch_uid) stackInit['branch_uid'] = stack.branch_uid;
|
|
278
273
|
if (stack.token) {
|
|
279
274
|
return managementAPIClient.stack({
|
|
280
275
|
...stackInit,
|
|
@@ -292,9 +287,159 @@ class ExportToCsvCommand extends Command {
|
|
|
292
287
|
.then(({ items }) => (items !== undefined ? items : []))
|
|
293
288
|
.catch((_err) => {});
|
|
294
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* check whether branch enabled org or not and update branch details
|
|
293
|
+
* @param {string} branchUid
|
|
294
|
+
* @param {object} stack
|
|
295
|
+
* @param {*} stackAPIClient
|
|
296
|
+
* @param {*} managementAPIClient
|
|
297
|
+
*/
|
|
298
|
+
async checkAndUpdateBranchDetail(branchUid, stack, stackAPIClient, managementAPIClient) {
|
|
299
|
+
if (branchUid) {
|
|
300
|
+
try {
|
|
301
|
+
const branchExists = await doesBranchExist(stackAPIClient, branchUid);
|
|
302
|
+
if (branchExists?.errorCode) {
|
|
303
|
+
throw new Error(branchExists.errorMessage);
|
|
304
|
+
}
|
|
305
|
+
stack.branch_uid = branchUid;
|
|
306
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (error?.message || error?.errorMessage) {
|
|
309
|
+
cliux.error(util.formatError(error));
|
|
310
|
+
this.exit();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
const stackBranches = await this.getStackBranches(stackAPIClient);
|
|
315
|
+
if (stackBranches === undefined) {
|
|
316
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
317
|
+
} else {
|
|
318
|
+
const { branch } = await util.chooseBranch(stackBranches);
|
|
319
|
+
stack.branch_uid = branch;
|
|
320
|
+
stackAPIClient = this.getStackClient(managementAPIClient, stack);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* fetch stack details from alias token
|
|
327
|
+
* @param {string} managementTokenAlias
|
|
328
|
+
* @param {string} stackName
|
|
329
|
+
* @returns
|
|
330
|
+
*/
|
|
331
|
+
async getAliasDetails(managementTokenAlias, stackName) {
|
|
332
|
+
let apiClient, stackDetails;
|
|
333
|
+
const listOfTokens = configHandler.get('tokens');
|
|
334
|
+
if (managementTokenAlias && listOfTokens[managementTokenAlias]) {
|
|
335
|
+
apiClient = await managementSDKClient({
|
|
336
|
+
host: this.cmaHost,
|
|
337
|
+
management_token: listOfTokens[managementTokenAlias].token,
|
|
338
|
+
});
|
|
339
|
+
stackDetails = {
|
|
340
|
+
name: stackName || managementTokenAlias,
|
|
341
|
+
apiKey: listOfTokens[managementTokenAlias].apiKey,
|
|
342
|
+
token: listOfTokens[managementTokenAlias].token,
|
|
343
|
+
};
|
|
344
|
+
} else if (managementTokenAlias) {
|
|
345
|
+
this.error('Provided management token alias not found in your config.!');
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
apiClient,
|
|
349
|
+
stackDetails,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* fetch stack details on basis of the selected org and stack
|
|
355
|
+
* @param {*} managementAPIClient
|
|
356
|
+
* @param {string} stackAPIKey
|
|
357
|
+
* @param {string} org
|
|
358
|
+
* @returns
|
|
359
|
+
*/
|
|
360
|
+
async getStackDetails(managementAPIClient, stackAPIKey, org) {
|
|
361
|
+
let organization, stackDetails;
|
|
362
|
+
|
|
363
|
+
if (!isAuthenticated()) {
|
|
364
|
+
this.error(config.CLI_EXPORT_CSV_ENTRIES_ERROR, {
|
|
365
|
+
exit: 2,
|
|
366
|
+
suggestions: ['https://www.contentstack.com/docs/developers/cli/authentication/'],
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (org) {
|
|
371
|
+
organization = { uid: org };
|
|
372
|
+
} else {
|
|
373
|
+
organization = await util.chooseOrganization(managementAPIClient); // prompt for organization
|
|
374
|
+
}
|
|
375
|
+
if (!stackAPIKey) {
|
|
376
|
+
stackDetails = await util.chooseStack(managementAPIClient, organization.uid); // prompt for stack
|
|
377
|
+
} else {
|
|
378
|
+
stackDetails = await util.chooseStack(managementAPIClient, organization.uid, stackAPIKey);
|
|
379
|
+
}
|
|
380
|
+
return stackDetails;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Create a taxonomies csv file for stack and a terms csv file for associated taxonomies
|
|
385
|
+
* @param {string} stackName
|
|
386
|
+
* @param {object} stack
|
|
387
|
+
* @param {string} taxUID
|
|
388
|
+
*/
|
|
389
|
+
async createTaxonomyAndTermCsvFile(stackAPIClient, stackName, stack, taxUID, delimiter) {
|
|
390
|
+
//TODO: Temp variable to export taxonomies in importable format will replaced with flag once decided
|
|
391
|
+
const importableCSV = true;
|
|
392
|
+
const payload = {
|
|
393
|
+
stackAPIClient,
|
|
394
|
+
type: '',
|
|
395
|
+
limit: config.limit || 100,
|
|
396
|
+
};
|
|
397
|
+
//check whether the taxonomy is valid or not
|
|
398
|
+
let taxonomies = [];
|
|
399
|
+
if (taxUID) {
|
|
400
|
+
payload['taxonomyUID'] = taxUID;
|
|
401
|
+
const taxonomy = await util.getTaxonomy(payload);
|
|
402
|
+
taxonomies.push(taxonomy);
|
|
403
|
+
} else {
|
|
404
|
+
taxonomies = await util.getAllTaxonomies(payload);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!importableCSV) {
|
|
408
|
+
const formattedTaxonomiesData = util.formatTaxonomiesData(taxonomies);
|
|
409
|
+
if (formattedTaxonomiesData?.length) {
|
|
410
|
+
const fileName = `${stackName ? stackName : stack.name}_taxonomies.csv`;
|
|
411
|
+
util.write(this, formattedTaxonomiesData, fileName, 'taxonomies', delimiter);
|
|
412
|
+
} else {
|
|
413
|
+
cliux.print('info: No taxonomies found! Please provide a valid stack.', { color: 'blue' });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (let index = 0; index < taxonomies?.length; index++) {
|
|
417
|
+
const taxonomy = taxonomies[index];
|
|
418
|
+
const taxonomyUID = taxonomy?.uid;
|
|
419
|
+
if (taxonomyUID) {
|
|
420
|
+
payload['taxonomyUID'] = taxonomyUID;
|
|
421
|
+
const terms = await util.getAllTermsOfTaxonomy(payload);
|
|
422
|
+
const formattedTermsData = util.formatTermsOfTaxonomyData(terms, taxonomyUID);
|
|
423
|
+
const taxonomyName = taxonomy?.name ?? '';
|
|
424
|
+
const termFileName = `${stackName ?? stack.name}_${taxonomyName}_${taxonomyUID}_terms.csv`;
|
|
425
|
+
if (formattedTermsData?.length) {
|
|
426
|
+
util.write(this, formattedTermsData, termFileName, 'terms', delimiter);
|
|
427
|
+
} else {
|
|
428
|
+
cliux.print(`info: No terms found for the taxonomy UID - '${taxonomyUID}'!`, { color: 'blue' });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
const fileName = `${stackName ?? stack.name}_taxonomies.csv`;
|
|
434
|
+
const { taxonomiesData, headers } = await util.createImportableCSV(payload, taxonomies);
|
|
435
|
+
if (taxonomiesData?.length) {
|
|
436
|
+
util.write(this, taxonomiesData, fileName, 'taxonomies',delimiter, headers);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
295
440
|
}
|
|
296
441
|
|
|
297
|
-
ExportToCsvCommand.description = `Export entries or organization users to csv using this command`;
|
|
442
|
+
ExportToCsvCommand.description = `Export entries, taxonomies, terms or organization users to csv using this command`;
|
|
298
443
|
|
|
299
444
|
ExportToCsvCommand.examples = [
|
|
300
445
|
'csdx cm:export-to-csv',
|
|
@@ -310,6 +455,30 @@ ExportToCsvCommand.examples = [
|
|
|
310
455
|
'',
|
|
311
456
|
'Exporting organization users to csv with organization name provided',
|
|
312
457
|
'csdx cm:export-to-csv --action <users> --org <org-uid> --org-name <org-name>',
|
|
458
|
+
'',
|
|
459
|
+
'Exporting Organizations Teams to CSV',
|
|
460
|
+
'csdx cm:export-to-csv --action <teams>',
|
|
461
|
+
'',
|
|
462
|
+
'Exporting Organizations Teams to CSV with org-uid',
|
|
463
|
+
'csdx cm:export-to-csv --action <teams> --org <org-uid>',
|
|
464
|
+
'',
|
|
465
|
+
'Exporting Organizations Teams to CSV with team uid',
|
|
466
|
+
'csdx cm:export-to-csv --action <teams> --team-uid <team-uid>',
|
|
467
|
+
'',
|
|
468
|
+
'Exporting Organizations Teams to CSV with org-uid and team uid',
|
|
469
|
+
'csdx cm:export-to-csv --action <teams> --org <org-uid> --team-uid <team-uid>',
|
|
470
|
+
'',
|
|
471
|
+
'Exporting Organizations Teams to CSV with org-uid and team uid',
|
|
472
|
+
'csdx cm:export-to-csv --action <teams> --org <org-uid> --team-uid <team-uid> --org-name <org-name>',
|
|
473
|
+
'',
|
|
474
|
+
'Exporting taxonomies and related terms to a .CSV file with the provided taxonomy UID',
|
|
475
|
+
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --taxonomy-uid <taxonomy-uid>',
|
|
476
|
+
'',
|
|
477
|
+
'Exporting taxonomies and respective terms to a .CSV file',
|
|
478
|
+
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias>',
|
|
479
|
+
'',
|
|
480
|
+
'Exporting taxonomies and respective terms to a .CSV file with a delimiter',
|
|
481
|
+
'csdx cm:export-to-csv --action <taxonomies> --alias <management-token-alias> --delimiter <delimiter>',
|
|
313
482
|
];
|
|
314
483
|
|
|
315
484
|
module.exports = ExportToCsvCommand;
|
package/src/util/config.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
+
limit:100,
|
|
2
3
|
cancelString: 'Cancel and Exit',
|
|
3
4
|
exportEntries: 'Export entries to a .CSV file',
|
|
4
|
-
exportUsers: "Export organization
|
|
5
|
+
exportUsers: "Export organization user's data to a .CSV file",
|
|
6
|
+
exportTeams: "Export organization team's data to a .csv file",
|
|
7
|
+
exportTaxonomies: 'Export taxonomies to a .CSV file',
|
|
5
8
|
adminError: "Unable to export data. Make sure you're an admin or owner of this organization",
|
|
6
9
|
organizationNameRegex: /\'/,
|
|
7
10
|
CLI_EXPORT_CSV_LOGIN_FAILED: "You need to login to execute this command. See: auth:login --help",
|
|
8
|
-
CLI_EXPORT_CSV_ENTRIES_ERROR: "You need to either login or provide a management token to execute this command"
|
|
9
|
-
|
|
11
|
+
CLI_EXPORT_CSV_ENTRIES_ERROR: "You need to either login or provide a management token to execute this command",
|
|
12
|
+
CLI_EXPORT_CSV_API_FAILED: 'Something went wrong! Please try again'
|
|
10
13
|
};
|
package/src/util/index.js
CHANGED
|
@@ -2,13 +2,22 @@ const os = require('os');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const mkdirp = require('mkdirp');
|
|
4
4
|
const find = require('lodash/find');
|
|
5
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
6
|
+
const omit = require('lodash/omit');
|
|
7
|
+
const flat = require('lodash/flatten');
|
|
5
8
|
const fastcsv = require('fast-csv');
|
|
6
9
|
const inquirer = require('inquirer');
|
|
7
10
|
const debug = require('debug')('export-to-csv');
|
|
8
11
|
const checkboxPlus = require('inquirer-checkbox-plus-prompt');
|
|
9
|
-
|
|
10
12
|
const config = require('./config.js');
|
|
11
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
cliux,
|
|
15
|
+
configHandler,
|
|
16
|
+
HttpClient,
|
|
17
|
+
messageHandler,
|
|
18
|
+
managementSDKClient,
|
|
19
|
+
ContentstackClient,
|
|
20
|
+
} = require('@contentstack/cli-utilities');
|
|
12
21
|
|
|
13
22
|
const directory = './data';
|
|
14
23
|
const delimeter = os.platform() === 'win32' ? '\\' : '/';
|
|
@@ -20,7 +29,7 @@ function chooseOrganization(managementAPIClient, action) {
|
|
|
20
29
|
return new Promise(async (resolve, reject) => {
|
|
21
30
|
try {
|
|
22
31
|
let organizations;
|
|
23
|
-
if (action === config.exportUsers) {
|
|
32
|
+
if (action === config.exportUsers || action === config.exportTeams || action === 'teams') {
|
|
24
33
|
organizations = await getOrganizationsWhereUserIsAdmin(managementAPIClient);
|
|
25
34
|
} else {
|
|
26
35
|
organizations = await getOrganizations(managementAPIClient);
|
|
@@ -105,7 +114,7 @@ async function getOrganizationsWhereUserIsAdmin(managementAPIClient) {
|
|
|
105
114
|
organizations.forEach((org) => {
|
|
106
115
|
result[org.name] = org.uid;
|
|
107
116
|
});
|
|
108
|
-
|
|
117
|
+
}
|
|
109
118
|
|
|
110
119
|
return result;
|
|
111
120
|
} catch (error) {
|
|
@@ -155,9 +164,7 @@ function chooseStack(managementAPIClient, orgUid, stackApiKey) {
|
|
|
155
164
|
|
|
156
165
|
async function chooseBranch(branchList) {
|
|
157
166
|
try {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const branchesArray = branches.map((branch) => branch.uid);
|
|
167
|
+
const branchesArray = branchList.map((branch) => branch.uid);
|
|
161
168
|
|
|
162
169
|
let _chooseBranch = [
|
|
163
170
|
{
|
|
@@ -321,7 +328,13 @@ function getEntries(stackAPIClient, contentType, language, skip, limit) {
|
|
|
321
328
|
stackAPIClient
|
|
322
329
|
.contentType(contentType)
|
|
323
330
|
.entry()
|
|
324
|
-
.query({
|
|
331
|
+
.query({
|
|
332
|
+
include_publish_details: true,
|
|
333
|
+
locale: language,
|
|
334
|
+
skip: skip * 100,
|
|
335
|
+
limit: limit,
|
|
336
|
+
include_workflow: true,
|
|
337
|
+
})
|
|
325
338
|
.find()
|
|
326
339
|
.then((entries) => resolve(entries))
|
|
327
340
|
.catch((error) => reject(error));
|
|
@@ -371,20 +384,20 @@ function exitProgram() {
|
|
|
371
384
|
process.exit();
|
|
372
385
|
}
|
|
373
386
|
|
|
374
|
-
function
|
|
387
|
+
function sanitizeData(flatData) {
|
|
375
388
|
// sanitize against CSV Injections
|
|
376
|
-
const CSVRegex = /^[\\+\\=@\\-]
|
|
377
|
-
for (key in
|
|
378
|
-
if (typeof
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
} else if (typeof
|
|
389
|
+
const CSVRegex = /^[\\+\\=@\\-]/;
|
|
390
|
+
for (key in flatData) {
|
|
391
|
+
if (typeof flatData[key] === 'string' && flatData[key].match(CSVRegex)) {
|
|
392
|
+
flatData[key] = flatData[key].replace(/\"/g, "\"\"");
|
|
393
|
+
flatData[key] = `"'${flatData[key]}"`;
|
|
394
|
+
} else if (typeof flatData[key] === 'object') {
|
|
382
395
|
// convert any objects or arrays to string
|
|
383
396
|
// to store this data correctly in csv
|
|
384
|
-
|
|
397
|
+
flatData[key] = JSON.stringify(flatData[key]);
|
|
385
398
|
}
|
|
386
399
|
}
|
|
387
|
-
return
|
|
400
|
+
return flatData;
|
|
388
401
|
}
|
|
389
402
|
|
|
390
403
|
function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
@@ -394,7 +407,7 @@ function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
|
394
407
|
return filteredEntries.map((entry) => {
|
|
395
408
|
let workflow = '';
|
|
396
409
|
const envArr = [];
|
|
397
|
-
if(entry
|
|
410
|
+
if (entry?.publish_details?.length) {
|
|
398
411
|
entry.publish_details.forEach((env) => {
|
|
399
412
|
envArr.push(JSON.stringify([environments[env['environment']], env['locale'], env['time']]));
|
|
400
413
|
});
|
|
@@ -403,13 +416,13 @@ function cleanEntries(entries, language, environments, contentTypeUid) {
|
|
|
403
416
|
delete entry.publish_details;
|
|
404
417
|
delete entry.setWorkflowStage;
|
|
405
418
|
if ('_workflow' in entry) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
419
|
+
if (entry._workflow?.name) {
|
|
420
|
+
workflow = entry['_workflow']['name'];
|
|
421
|
+
delete entry['_workflow'];
|
|
422
|
+
}
|
|
410
423
|
}
|
|
411
424
|
entry = flatten(entry);
|
|
412
|
-
entry =
|
|
425
|
+
entry = sanitizeData(entry);
|
|
413
426
|
entry['publish_details'] = envArr;
|
|
414
427
|
entry['_workflow'] = workflow;
|
|
415
428
|
entry['ACL'] = JSON.stringify({}); // setting ACL to empty obj
|
|
@@ -437,7 +450,7 @@ function getDateTime() {
|
|
|
437
450
|
return dateTime.join('_');
|
|
438
451
|
}
|
|
439
452
|
|
|
440
|
-
function write(command, entries, fileName, message) {
|
|
453
|
+
function write(command, entries, fileName, message, delimiter, headers) {
|
|
441
454
|
// eslint-disable-next-line no-undef
|
|
442
455
|
if (process.cwd().split(delimeter).pop() !== 'data' && !fs.existsSync(directory)) {
|
|
443
456
|
mkdirp.sync(directory);
|
|
@@ -449,7 +462,8 @@ function write(command, entries, fileName, message) {
|
|
|
449
462
|
}
|
|
450
463
|
// eslint-disable-next-line no-undef
|
|
451
464
|
cliux.print(`Writing ${message} to file: ${process.cwd()}${delimeter}${fileName}`);
|
|
452
|
-
fastcsv.writeToPath(fileName, entries, { headers
|
|
465
|
+
if (headers?.length) fastcsv.writeToPath(fileName, entries, { headers, delimiter });
|
|
466
|
+
else fastcsv.writeToPath(fileName, entries, { headers: true, delimiter });
|
|
453
467
|
}
|
|
454
468
|
|
|
455
469
|
function startupQuestions() {
|
|
@@ -459,7 +473,7 @@ function startupQuestions() {
|
|
|
459
473
|
type: 'list',
|
|
460
474
|
name: 'action',
|
|
461
475
|
message: 'Choose Action',
|
|
462
|
-
choices: [config.exportEntries, config.exportUsers, 'Exit'],
|
|
476
|
+
choices: [config.exportEntries, config.exportUsers, config.exportTeams, config.exportTaxonomies, 'Exit'],
|
|
463
477
|
},
|
|
464
478
|
];
|
|
465
479
|
inquirer
|
|
@@ -678,6 +692,479 @@ function wait(time) {
|
|
|
678
692
|
});
|
|
679
693
|
}
|
|
680
694
|
|
|
695
|
+
function handleErrorMsg(err) {
|
|
696
|
+
cliux.print(`Error: ${(err?.errorMessage || err?.message) ? err?.errorMessage || err?.message : messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' })
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async function apiRequestHandler(org, queryParam = {}) {
|
|
701
|
+
const headers = {
|
|
702
|
+
authtoken: configHandler.get('authtoken'),
|
|
703
|
+
organization_uid: org.uid,
|
|
704
|
+
'Content-Type': 'application/json',
|
|
705
|
+
api_version: 1.1,
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
return await new HttpClient()
|
|
709
|
+
.headers(headers)
|
|
710
|
+
.queryParams(queryParam)
|
|
711
|
+
.get(`${configHandler.get('region')?.cma}/organizations/${org?.uid}/teams`)
|
|
712
|
+
.then((res) => {
|
|
713
|
+
const { status, data } = res;
|
|
714
|
+
if (status === 200) {
|
|
715
|
+
return data;
|
|
716
|
+
} else {
|
|
717
|
+
cliux.print(`${data?.error_message || data?.message || data?.errorMessage}`, { color: 'red' });
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
.catch((error) => {
|
|
722
|
+
handleErrorMsg(error);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async function exportOrgTeams(managementAPIClient, org) {
|
|
727
|
+
let teamsObjectArray = [];
|
|
728
|
+
let skip = 0;
|
|
729
|
+
let limit = config?.limit || 100;
|
|
730
|
+
do {
|
|
731
|
+
const data = await apiRequestHandler(org, { skip: skip, limit: limit, includeUserDetails: true });
|
|
732
|
+
skip += limit;
|
|
733
|
+
teamsObjectArray.push(...data?.teams);
|
|
734
|
+
if (skip >= data?.count) break;
|
|
735
|
+
} while (1);
|
|
736
|
+
teamsObjectArray = await cleanTeamsData(teamsObjectArray, managementAPIClient, org);
|
|
737
|
+
return teamsObjectArray;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async function getOrgRolesForTeams(managementAPIClient, org) {
|
|
741
|
+
let roleMap = {}; // for org level there are two roles only admin and member
|
|
742
|
+
|
|
743
|
+
// SDK call to get the role uids
|
|
744
|
+
await managementAPIClient
|
|
745
|
+
.organization(org.uid)
|
|
746
|
+
.roles()
|
|
747
|
+
.then((roles) => {
|
|
748
|
+
roles.items.forEach((item) => {
|
|
749
|
+
if (item.name === 'member' || item.name === 'admin') {
|
|
750
|
+
roleMap[item.name] = item.uid;
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
})
|
|
754
|
+
.catch((err) => {
|
|
755
|
+
handleErrorMsg(err);
|
|
756
|
+
});
|
|
757
|
+
return roleMap;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async function cleanTeamsData(data, managementAPIClient, org) {
|
|
761
|
+
const roleMap = await getOrgRolesForTeams(managementAPIClient, org);
|
|
762
|
+
const fieldToBeDeleted = [
|
|
763
|
+
'_id',
|
|
764
|
+
'createdAt',
|
|
765
|
+
'createdBy',
|
|
766
|
+
'updatedAt',
|
|
767
|
+
'updatedBy',
|
|
768
|
+
'__v',
|
|
769
|
+
'createdByUserName',
|
|
770
|
+
'updatedByUserName',
|
|
771
|
+
'organizationUid',
|
|
772
|
+
];
|
|
773
|
+
if (data?.length) {
|
|
774
|
+
return data.map((team) => {
|
|
775
|
+
team = omit(team, fieldToBeDeleted);
|
|
776
|
+
|
|
777
|
+
team.organizationRole = (team.organizationRole === roleMap["member"]) ? "member" : "admin";
|
|
778
|
+
|
|
779
|
+
if (!team.hasOwnProperty("description")) {
|
|
780
|
+
team.description = "";
|
|
781
|
+
}
|
|
782
|
+
team.Total_Members = team?.users?.length || 0;
|
|
783
|
+
|
|
784
|
+
return team;
|
|
785
|
+
});
|
|
786
|
+
} else {
|
|
787
|
+
return [];
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
async function exportTeams(managementAPIClient, organization, teamUid, delimiter) {
|
|
792
|
+
cliux.print(
|
|
793
|
+
`info: Exporting the ${
|
|
794
|
+
teamUid && organization?.name
|
|
795
|
+
? `team with uid ${teamUid} in Organisation ${organization?.name} `
|
|
796
|
+
: `teams of Organisation ` + organization?.name
|
|
797
|
+
}`,
|
|
798
|
+
{ color: 'blue' },
|
|
799
|
+
);
|
|
800
|
+
const allTeamsData = await exportOrgTeams(managementAPIClient, organization);
|
|
801
|
+
if (!allTeamsData?.length) {
|
|
802
|
+
cliux.print(`info: The organization ${organization?.name} does not have any teams associated with it. Please verify and provide the correct organization name.`);
|
|
803
|
+
} else {
|
|
804
|
+
const modifiedTeam = cloneDeep(allTeamsData);
|
|
805
|
+
modifiedTeam.forEach((team) => {
|
|
806
|
+
delete team['users'];
|
|
807
|
+
delete team['stackRoleMapping'];
|
|
808
|
+
});
|
|
809
|
+
const fileName = `${kebabize(organization.name.replace(config.organizationNameRegex, ''))}_teams_export.csv`;
|
|
810
|
+
write(this, modifiedTeam, fileName, ' organization Team details', delimiter);
|
|
811
|
+
// exporting teams user data or a single team user data
|
|
812
|
+
cliux.print(
|
|
813
|
+
`info: Exporting the teams user data for ${teamUid ? `team ` + teamUid : `organisation ` + organization?.name}`,
|
|
814
|
+
{ color: 'blue' },
|
|
815
|
+
);
|
|
816
|
+
await getTeamsDetail(allTeamsData, organization, teamUid, delimiter);
|
|
817
|
+
cliux.print(
|
|
818
|
+
`info: Exporting the stack role details for ${
|
|
819
|
+
teamUid ? `team ` + teamUid : `organisation ` + organization?.name
|
|
820
|
+
}`,
|
|
821
|
+
{ color: 'blue' },
|
|
822
|
+
);
|
|
823
|
+
// Exporting the stack Role data for all the teams or exporting stack role data for a single team
|
|
824
|
+
await exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
async function getTeamsDetail(allTeamsData, organization, teamUid, delimiter) {
|
|
829
|
+
if (!teamUid) {
|
|
830
|
+
const userData = await getTeamsUserDetails(allTeamsData);
|
|
831
|
+
const fileName = `${kebabize(
|
|
832
|
+
organization.name.replace(config.organizationNameRegex, ''),
|
|
833
|
+
)}_team_User_Details_export.csv`;
|
|
834
|
+
|
|
835
|
+
write(this, userData, fileName, 'Team User details', delimiter);
|
|
836
|
+
} else {
|
|
837
|
+
const team = allTeamsData.filter((team) => team.uid === teamUid)[0];
|
|
838
|
+
|
|
839
|
+
team.users.forEach((user) => {
|
|
840
|
+
user['team-name'] = team.name;
|
|
841
|
+
user['team-uid'] = team.uid;
|
|
842
|
+
delete user['active'];
|
|
843
|
+
delete user['orgInvitationStatus'];
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
const fileName = `${kebabize(
|
|
847
|
+
organization.name.replace(config.organizationNameRegex, ''),
|
|
848
|
+
)}_team_${teamUid}_User_Details_export.csv`;
|
|
849
|
+
|
|
850
|
+
write(this, team.users, fileName, 'Team User details', delimiter);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
async function exportRoleMappings(managementAPIClient, allTeamsData, teamUid, delimiter) {
|
|
855
|
+
let stackRoleWithTeamData = [];
|
|
856
|
+
let flag = false;
|
|
857
|
+
const stackNotAdmin = [];
|
|
858
|
+
if (teamUid) {
|
|
859
|
+
const team = find(allTeamsData,function(teamObject) { return teamObject?.uid===teamUid });
|
|
860
|
+
for (const stack of team?.stackRoleMapping) {
|
|
861
|
+
const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid);
|
|
862
|
+
stackRoleWithTeamData.push(...roleData);
|
|
863
|
+
if(roleData[0]['Stack Name']==='') {
|
|
864
|
+
flag = true;
|
|
865
|
+
stackNotAdmin.push(stack.stackApiKey);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
for (const team of allTeamsData ?? []) {
|
|
870
|
+
for (const stack of team?.stackRoleMapping ?? []) {
|
|
871
|
+
const roleData = await mapRoleWithTeams(managementAPIClient, stack, team?.name, team?.uid);
|
|
872
|
+
stackRoleWithTeamData.push(...roleData);
|
|
873
|
+
if(roleData[0]['Stack Name']==='') {
|
|
874
|
+
flag = true;
|
|
875
|
+
stackNotAdmin.push(stack.stackApiKey);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if(stackNotAdmin?.length) {
|
|
881
|
+
cliux.print(`warning: Admin access denied to the following stacks using the provided API keys. Please get in touch with the stack owner to request access.`,{color:"yellow"});
|
|
882
|
+
cliux.print(`${stackNotAdmin.join(' , ')}`,{color:"yellow"});
|
|
883
|
+
}
|
|
884
|
+
if(flag) {
|
|
885
|
+
let export_stack_role = [
|
|
886
|
+
{
|
|
887
|
+
type: 'list',
|
|
888
|
+
name: 'chooseExport',
|
|
889
|
+
message: `Access denied: Please confirm if you still want to continue exporting the data without the { Stack Name, Stack Uid, Role Name } fields.`,
|
|
890
|
+
choices: ['yes', 'no'],
|
|
891
|
+
loop: false,
|
|
892
|
+
}]
|
|
893
|
+
const exportStackRole = await inquirer
|
|
894
|
+
.prompt(export_stack_role)
|
|
895
|
+
.then(( chosenOrg ) => {
|
|
896
|
+
return chosenOrg
|
|
897
|
+
})
|
|
898
|
+
.catch((error) => {
|
|
899
|
+
cliux.print(error, {color:'red'});
|
|
900
|
+
process.exit(1);
|
|
901
|
+
});
|
|
902
|
+
if(exportStackRole.chooseExport === 'no') {
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const fileName = `${kebabize('Stack_Role_Mapping'.replace(config.organizationNameRegex, ''))}${
|
|
908
|
+
teamUid ? `_${teamUid}` : ''
|
|
909
|
+
}.csv`;
|
|
910
|
+
|
|
911
|
+
write(this, stackRoleWithTeamData, fileName, 'Team Stack Role details', delimiter);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async function mapRoleWithTeams(managementAPIClient, stackRoleMapping, teamName, teamUid) {
|
|
915
|
+
const roles = await getRoleData(managementAPIClient, stackRoleMapping.stackApiKey);
|
|
916
|
+
const stackRole = {};
|
|
917
|
+
roles?.items?.forEach((role) => {
|
|
918
|
+
if (!stackRole.hasOwnProperty(role?.uid)) {
|
|
919
|
+
stackRole[role?.uid] = role?.name;
|
|
920
|
+
stackRole[role?.stack?.api_key] = {name: role?.stack?.name, uid: role?.stack?.uid }
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
const stackRoleMapOfTeam = stackRoleMapping?.roles.map((role) => {
|
|
924
|
+
return {
|
|
925
|
+
'Team Name': teamName,
|
|
926
|
+
'Team Uid': teamUid,
|
|
927
|
+
'Stack Name': stackRole[stackRoleMapping?.stackApiKey]?.name || '',
|
|
928
|
+
'Stack Uid': stackRole[stackRoleMapping?.stackApiKey]?.uid || '',
|
|
929
|
+
'Role Name': stackRole[role] || '',
|
|
930
|
+
'Role Uid': role || '',
|
|
931
|
+
};
|
|
932
|
+
});
|
|
933
|
+
return stackRoleMapOfTeam;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async function getRoleData(managementAPIClient, stackApiKey) {
|
|
937
|
+
try {
|
|
938
|
+
return await managementAPIClient.stack({ api_key: stackApiKey }).role().fetchAll();
|
|
939
|
+
} catch (error) {
|
|
940
|
+
return {}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async function getTeamsUserDetails(teamsObject) {
|
|
945
|
+
const allTeamUsers = [];
|
|
946
|
+
teamsObject.forEach((team) => {
|
|
947
|
+
if (team?.users?.length) {
|
|
948
|
+
team.users.forEach((user) => {
|
|
949
|
+
user['team-name'] = team.name;
|
|
950
|
+
user['team-uid'] = team.uid;
|
|
951
|
+
delete user['active'];
|
|
952
|
+
delete user['orgInvitationStatus'];
|
|
953
|
+
allTeamUsers.push(user);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
return allTeamUsers;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* fetch all taxonomies in the provided stack
|
|
962
|
+
* @param {object} payload
|
|
963
|
+
* @param {number} skip
|
|
964
|
+
* @param {array} taxonomies
|
|
965
|
+
* @returns
|
|
966
|
+
*/
|
|
967
|
+
async function getAllTaxonomies(payload, skip = 0, taxonomies = []) {
|
|
968
|
+
payload['type'] = 'taxonomies';
|
|
969
|
+
const { items, count } = await taxonomySDKHandler(payload, skip);
|
|
970
|
+
if (items) {
|
|
971
|
+
skip += payload.limit;
|
|
972
|
+
taxonomies.push(...items);
|
|
973
|
+
if (skip >= count) {
|
|
974
|
+
return taxonomies;
|
|
975
|
+
} else {
|
|
976
|
+
return getAllTaxonomies(payload, skip, taxonomies);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return taxonomies;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* fetch taxonomy related terms
|
|
984
|
+
* @param {object} payload
|
|
985
|
+
* @param {number} skip
|
|
986
|
+
* @param {number} limit
|
|
987
|
+
* @param {array} terms
|
|
988
|
+
* @returns
|
|
989
|
+
*/
|
|
990
|
+
async function getAllTermsOfTaxonomy(payload, skip = 0, terms = []) {
|
|
991
|
+
payload['type'] = 'terms';
|
|
992
|
+
const { items, count } = await taxonomySDKHandler(payload, skip);
|
|
993
|
+
if (items) {
|
|
994
|
+
skip += payload.limit;
|
|
995
|
+
terms.push(...items);
|
|
996
|
+
if (skip >= count) {
|
|
997
|
+
return terms;
|
|
998
|
+
} else {
|
|
999
|
+
return getAllTermsOfTaxonomy(payload, skip, terms);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return terms;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Verify the existence of a taxonomy. Obtain its details if it exists and return
|
|
1007
|
+
* @param {object} payload
|
|
1008
|
+
* @param {string} taxonomyUID
|
|
1009
|
+
* @returns
|
|
1010
|
+
*/
|
|
1011
|
+
async function getTaxonomy(payload) {
|
|
1012
|
+
payload['type'] = 'taxonomy';
|
|
1013
|
+
const resp = await taxonomySDKHandler(payload);
|
|
1014
|
+
return resp;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* taxonomy & term sdk handler
|
|
1019
|
+
* @async
|
|
1020
|
+
* @method
|
|
1021
|
+
* @param payload
|
|
1022
|
+
* @param skip
|
|
1023
|
+
* @param limit
|
|
1024
|
+
* @returns {*} Promise<any>
|
|
1025
|
+
*/
|
|
1026
|
+
async function taxonomySDKHandler(payload, skip) {
|
|
1027
|
+
const { stackAPIClient, taxonomyUID, type } = payload;
|
|
1028
|
+
|
|
1029
|
+
const queryParams = { include_count: true, limit: payload.limit };
|
|
1030
|
+
if (skip >= 0) queryParams['skip'] = skip || 0;
|
|
1031
|
+
|
|
1032
|
+
switch (type) {
|
|
1033
|
+
case 'taxonomies':
|
|
1034
|
+
return await stackAPIClient
|
|
1035
|
+
.taxonomy()
|
|
1036
|
+
.query(queryParams)
|
|
1037
|
+
.find()
|
|
1038
|
+
.then((data) => data)
|
|
1039
|
+
.catch((err) => handleErrorMsg(err));
|
|
1040
|
+
case 'taxonomy':
|
|
1041
|
+
return await stackAPIClient
|
|
1042
|
+
.taxonomy(taxonomyUID)
|
|
1043
|
+
.fetch()
|
|
1044
|
+
.then((data) => data)
|
|
1045
|
+
.catch((err) => handleErrorMsg(err));
|
|
1046
|
+
case 'terms':
|
|
1047
|
+
queryParams['depth'] = 0;
|
|
1048
|
+
return await stackAPIClient
|
|
1049
|
+
.taxonomy(taxonomyUID)
|
|
1050
|
+
.terms()
|
|
1051
|
+
.query(queryParams)
|
|
1052
|
+
.find()
|
|
1053
|
+
.then((data) => data)
|
|
1054
|
+
.catch((err) => handleErrorMsg(err));
|
|
1055
|
+
default:
|
|
1056
|
+
handleErrorMsg({ errorMessage: 'Invalid module!' });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Change taxonomies data in required CSV headers format
|
|
1062
|
+
* @param {array} taxonomies
|
|
1063
|
+
* @returns
|
|
1064
|
+
*/
|
|
1065
|
+
function formatTaxonomiesData(taxonomies) {
|
|
1066
|
+
if (taxonomies?.length) {
|
|
1067
|
+
const formattedTaxonomies = taxonomies.map((taxonomy) => {
|
|
1068
|
+
return sanitizeData({
|
|
1069
|
+
'Taxonomy UID': taxonomy.uid,
|
|
1070
|
+
Name: taxonomy.name,
|
|
1071
|
+
Description: taxonomy.description,
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
return formattedTaxonomies;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Modify the linked taxonomy data's terms in required CSV headers format
|
|
1080
|
+
* @param {array} terms
|
|
1081
|
+
* @param {string} taxonomyUID
|
|
1082
|
+
* @returns
|
|
1083
|
+
*/
|
|
1084
|
+
function formatTermsOfTaxonomyData(terms, taxonomyUID) {
|
|
1085
|
+
if (terms?.length) {
|
|
1086
|
+
const formattedTerms = terms.map((term) => {
|
|
1087
|
+
return sanitizeData({
|
|
1088
|
+
'Taxonomy UID': taxonomyUID,
|
|
1089
|
+
UID: term.uid,
|
|
1090
|
+
Name: term.name,
|
|
1091
|
+
'Parent UID': term.parent_uid,
|
|
1092
|
+
Depth: term.depth,
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
return formattedTerms;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function handleErrorMsg(err) {
|
|
1100
|
+
if (err?.errorMessage) {
|
|
1101
|
+
cliux.print(`Error: ${err.errorMessage}`, { color: 'red' });
|
|
1102
|
+
} else if (err?.message) {
|
|
1103
|
+
const errorMsg = err?.errors?.taxonomy || err?.errors?.term || err?.message;
|
|
1104
|
+
cliux.print(`Error: ${errorMsg}`, { color: 'red' });
|
|
1105
|
+
} else {
|
|
1106
|
+
console.log(err);
|
|
1107
|
+
cliux.print(`Error: ${messageHandler.parse('CLI_EXPORT_CSV_API_FAILED')}`, { color: 'red' });
|
|
1108
|
+
}
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* create an importable CSV file, to utilize with the migration script.
|
|
1114
|
+
* @param {*} payload api request payload
|
|
1115
|
+
* @param {*} taxonomies taxonomies data
|
|
1116
|
+
* @returns
|
|
1117
|
+
*/
|
|
1118
|
+
async function createImportableCSV(payload, taxonomies) {
|
|
1119
|
+
let taxonomiesData = [];
|
|
1120
|
+
let headers = ['Taxonomy Name','Taxonomy UID','Taxonomy Description'];
|
|
1121
|
+
for (let index = 0; index < taxonomies?.length; index++) {
|
|
1122
|
+
const taxonomy = taxonomies[index];
|
|
1123
|
+
const taxonomyUID = taxonomy?.uid;
|
|
1124
|
+
if (taxonomyUID) {
|
|
1125
|
+
const sanitizedTaxonomy = sanitizeData({
|
|
1126
|
+
'Taxonomy Name': taxonomy?.name,
|
|
1127
|
+
'Taxonomy UID': taxonomyUID,
|
|
1128
|
+
'Taxonomy Description': taxonomy?.description,
|
|
1129
|
+
});
|
|
1130
|
+
taxonomiesData.push(sanitizedTaxonomy);
|
|
1131
|
+
payload['taxonomyUID'] = taxonomyUID;
|
|
1132
|
+
const terms = await getAllTermsOfTaxonomy(payload);
|
|
1133
|
+
//fetch all parent terms
|
|
1134
|
+
const parentTerms = terms.filter((term) => term?.parent_uid === null);
|
|
1135
|
+
const termsData = getParentAndChildTerms(parentTerms, terms, headers);
|
|
1136
|
+
taxonomiesData.push(...termsData)
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
return {taxonomiesData, headers};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Get the parent and child terms, then arrange them hierarchically in a CSV file.
|
|
1145
|
+
* @param {*} parentTerms list of parent terms
|
|
1146
|
+
* @param {*} terms respective terms of taxonomies
|
|
1147
|
+
* @param {*} headers list of csv headers include taxonomy and terms column
|
|
1148
|
+
* @param {*} termsData parent and child terms
|
|
1149
|
+
*/
|
|
1150
|
+
function getParentAndChildTerms(parentTerms, terms, headers, termsData=[]) {
|
|
1151
|
+
for (let i = 0; i < parentTerms?.length; i++) {
|
|
1152
|
+
const parentTerm = parentTerms[i];
|
|
1153
|
+
const levelUID = `Term Level${parentTerm.depth} UID`;
|
|
1154
|
+
const levelName = `Term Level${parentTerm.depth} Name`;
|
|
1155
|
+
if (headers.indexOf(levelName) === -1) headers.push(levelName);
|
|
1156
|
+
if (headers.indexOf(levelUID) === -1) headers.push(levelUID);
|
|
1157
|
+
const sanitizedTermData = sanitizeData({ [levelName]: parentTerm.name, [levelUID]: parentTerm.uid });
|
|
1158
|
+
termsData.push(sanitizedTermData);
|
|
1159
|
+
//fetch all sibling terms
|
|
1160
|
+
const newParents = terms.filter((term) => term.parent_uid === parentTerm.uid);
|
|
1161
|
+
if (newParents?.length) {
|
|
1162
|
+
getParentAndChildTerms(newParents, terms, headers, termsData);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return termsData;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
681
1168
|
module.exports = {
|
|
682
1169
|
chooseOrganization: chooseOrganization,
|
|
683
1170
|
chooseStack: chooseStack,
|
|
@@ -704,4 +1191,13 @@ module.exports = {
|
|
|
704
1191
|
chooseInMemContentTypes: chooseInMemContentTypes,
|
|
705
1192
|
getEntriesCount: getEntriesCount,
|
|
706
1193
|
formatError: formatError,
|
|
1194
|
+
exportOrgTeams: exportOrgTeams,
|
|
1195
|
+
exportTeams: exportTeams,
|
|
1196
|
+
getAllTaxonomies,
|
|
1197
|
+
getAllTermsOfTaxonomy,
|
|
1198
|
+
formatTaxonomiesData,
|
|
1199
|
+
formatTermsOfTaxonomyData,
|
|
1200
|
+
getTaxonomy,
|
|
1201
|
+
getStacks,
|
|
1202
|
+
createImportableCSV,
|
|
707
1203
|
};
|