Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CS-43976 - added audit for workflow #1306

Merged
merged 19 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/contentstack-audit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-audit",
"version": "1.4.0",
"version": "1.5.0",
"description": "Contentstack audit plugin",
"author": "Contentstack CLI",
"homepage": "https://github.com/contentstack/cli",
Expand Down
89 changes: 79 additions & 10 deletions packages/contentstack-audit/src/audit-base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import config from './config';
import { print } from './util/log';
import { auditMsg } from './messages';
import { BaseCommand } from './base-command';
import { Entries, GlobalField, ContentType } from './modules';
import { Entries, GlobalField, ContentType, Workflows } from './modules';
import { CommandNames, ContentTypeStruct, OutputColumn, RefErrorReturnType } from './types';

export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseCommand> {
Expand All @@ -31,7 +31,7 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
}

/**
* The `start` function performs an audit on content types, global fields, and entries, and displays
* The `start` function performs an audit on content types, global fields, entries, and workflows and displays
* any missing references.
* @param {string} command - The `command` parameter is a string that represents the current command
* being executed.
Expand All @@ -42,15 +42,22 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
await this.createBackUp();
this.sharedConfig.reportPath = resolve(this.flags['report-path'] || process.cwd(), 'audit-report');

const { missingCtRefs, missingGfRefs, missingEntryRefs } = await this.scanAndFix();
const { missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInWorkflow } = await this.scanAndFix();

this.showOutputOnScreen([
{ module: 'Content types', missingRefs: missingCtRefs },
{ module: 'Global Fields', missingRefs: missingGfRefs },
{ module: 'Entries', missingRefs: missingEntryRefs },
]);

if (!isEmpty(missingCtRefs) || !isEmpty(missingGfRefs) || !isEmpty(missingEntryRefs)) {
this.showOutputOnScreenWorkflowsAndExtension([{ module: 'Workflows', missingRefs: missingCtRefsInWorkflow }]);

if (
!isEmpty(missingCtRefs) ||
!isEmpty(missingGfRefs) ||
!isEmpty(missingEntryRefs) ||
!isEmpty(missingCtRefsInWorkflow)
) {
if (this.currentCommand === 'cm:stacks:audit') {
this.log(this.$t(auditMsg.FINAL_REPORT_PATH, { path: this.sharedConfig.reportPath }), 'warn');
} else {
Expand All @@ -70,7 +77,12 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
}
}

return !isEmpty(missingCtRefs) || !isEmpty(missingGfRefs) || !isEmpty(missingEntryRefs);
return (
!isEmpty(missingCtRefs) ||
!isEmpty(missingGfRefs) ||
!isEmpty(missingEntryRefs) ||
!isEmpty(missingCtRefsInWorkflow)
);
}

/**
Expand All @@ -81,7 +93,7 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
*/
async scanAndFix() {
let { ctSchema, gfSchema } = this.getCtAndGfSchema();
let missingCtRefs, missingGfRefs, missingEntryRefs;
let missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInWorkflow;
for (const module of this.sharedConfig.flags.modules || this.sharedConfig.modules) {
ux.action.start(this.$t(this.messages.AUDIT_START_SPINNER, { module }));

Expand All @@ -107,12 +119,22 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
missingEntryRefs = await new Entries(cloneDeep(constructorParam)).run();
await this.prepareReport(module, missingEntryRefs);
break;
case 'workflows':
missingCtRefsInWorkflow = await new Workflows({
ctSchema,
log: this.log,
moduleName: module,
config: this.sharedConfig,
fix: this.currentCommand === 'cm:stacks:audit:fix',
}).run();
await this.prepareReport(module, missingCtRefsInWorkflow);
break;
}

ux.action.stop();
}

return { missingCtRefs, missingGfRefs, missingEntryRefs };
return { missingCtRefs, missingGfRefs, missingEntryRefs, missingCtRefsInWorkflow };
}

/**
Expand Down Expand Up @@ -241,6 +263,52 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
}
}

// Make it generic it takes the column header as param
showOutputOnScreenWorkflowsAndExtension(allMissingRefs: { module: string; missingRefs?: Record<string, any> }[]) {
if (this.sharedConfig.showTerminalOutput && !this.flags['external-config']?.noTerminalOutput) {
this.log(''); // NOTE adding new line
for (const { module, missingRefs } of allMissingRefs) {
if (!isEmpty(missingRefs)) {
print([
{
bold: true,
color: 'cyan',
message: ` ${module}`,
},
]);
const tableValues = Object.values(missingRefs).flat();
ux.table(
tableValues,
{
name: {
minWidth: 7,
header: 'Title',
},
uid: {
minWidth: 12,
header: 'Workflow Uid',
},
content_types: {
minWidth: 7,
header: 'Missing Content Types',
get: (row) => {
return chalk.red(
typeof row.content_types === 'object' ? JSON.stringify(row.content_types) : row.content_types,
);
},
},
...(tableValues[0]?.fixStatus ? this.fixStatus : {}),
},
{
...this.flags,
},
);
this.log(''); // NOTE adding new line
}
}
}
}

/**
* The function prepares a report by writing a JSON file and a CSV file with a list of missing
* references for a given module.
Expand Down Expand Up @@ -299,8 +367,10 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma
let row: Record<string, string | string[]> = {};

for (const column of columns) {
row[column] = issue[OutputColumn[column]];
row[column] = typeof row[column] === 'object' ? JSON.stringify(row[column]) : row[column];
if (Object.keys(issue).includes(OutputColumn[column])) {
row[column] = issue[OutputColumn[column]] as string;
row[column] = typeof row[column] === 'object' ? JSON.stringify(row[column]) : row[column];
}
}

if (this.currentCommand === 'cm:stacks:audit:fix') {
Expand All @@ -309,7 +379,6 @@ export abstract class AuditBaseCommand extends BaseCommand<typeof AuditBaseComma

rowData.push(row);
}

csv.write(rowData, { headers: true }).pipe(ws).on('error', reject).on('finish', resolve);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default class Audit extends AuditBaseCommand {

/**
* The `run` function is an asynchronous function that performs an audit on different modules
* (content-types, global-fields, entries) and generates a report.
* (content-types, global-fields, entries, workflows) and generates a report.
*/
async run(): Promise<void> {
try {
Expand Down
9 changes: 7 additions & 2 deletions packages/contentstack-audit/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const config = {
showTerminalOutput: true,
skipRefs: ['sys_assets'],
skipFieldTypes: ['taxonomy'],
modules: ['content-types', 'global-fields', 'entries'],
'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group'],
modules: ['content-types', 'global-fields', 'entries', 'workflows'],
'fix-fields': ['reference', 'global_field', 'json:rte', 'json:extension', 'blocks', 'group', 'content_types'],
moduleConfig: {
'content-types': {
name: 'content type',
Expand All @@ -25,6 +25,11 @@ const config = {
dirName: 'locales',
fileName: 'locales.json',
},
workflows: {
name: 'workflows',
dirName: 'workflows',
fileName: 'workflows.json',
},
},
entries: {
systemKeys: [
Expand Down
4 changes: 4 additions & 0 deletions packages/contentstack-audit/src/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const commonMsg = {
CONFIG: 'Path of the external config',
DATA_DIR: 'Path where the data is stored',
FIX_CONFIRMATION: 'Would you like to overwrite existing file.?',
WORKFLOW_FIX_WARN: `Workflow {uid} named '{name}' will be removed.`,
WORKFLOW_FIX_CONFIRMATION: 'Would you like to overwrite existing file.?',
};

const auditMsg = {
Expand All @@ -26,6 +28,7 @@ const auditMsg = {
SCAN_CT_SUCCESS_MSG: "Successfully completed the scanning of {module} '{title}'.",
SCAN_ENTRY_SUCCESS_MSG: "Successfully completed the scanning of {module} ({local}) '{title}'.",
AUDIT_CMD_DESCRIPTION: 'Perform audits and find possible errors in the exported Contentstack data',
SCAN_WF_SUCCESS_MSG: "Successfully completed the scanning of {module} '{name}'.",
};

const auditFixMsg = {
Expand All @@ -35,6 +38,7 @@ const auditFixMsg = {
FIXED_CONTENT_PATH_MAG: 'You can locate the fixed content at {path}.',
EMPTY_FIX_MSG: 'Successfully removed the empty field/block found at {path} from the schema.',
AUDIT_FIX_CMD_DESCRIPTION: 'Perform audits and fix possible errors in the exported Contentstack data.',
WF_FIX_MSG: 'Successfully removed the workflow {uid} named {name}.',
};

const messages: typeof errors &
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-audit/src/modules/content-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ export default class ContentType {
treeStr: tree.map(({ name }) => name).join(' ➜ '),
});

return null
return null;
}

return field;
Expand Down
3 changes: 2 additions & 1 deletion packages/contentstack-audit/src/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Entries from "./entries"
import GlobalField from "./global-fields"
import ContentType from "./content-types"
import Workflows from "./workflows"

export { Entries, GlobalField, ContentType }
export { Entries, GlobalField, ContentType, Workflows }
Loading
Loading