Skip to content

Commit

Permalink
feat: add flag to support delete changes (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
y-lakhdar authored Jun 17, 2021
1 parent 4c90fd1 commit e0c9aad
Show file tree
Hide file tree
Showing 11 changed files with 520 additions and 79 deletions.
246 changes: 246 additions & 0 deletions packages/cli/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"jest": "^26.6.3",
"prettier": "^2.3.0",
"rimraf": "^3.0.2",
"strip-ansi": "^6.0.0",
"ts-jest": "^26.5.1",
"ts-node": "^8",
"typescript": "4.2.3"
Expand Down
23 changes: 19 additions & 4 deletions packages/cli/src/commands/org/config/preview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const mockedDeleteTemporaryZipFile = jest.fn();
const mockedDeleteSnapshot = jest.fn();
const mockedSaveDetailedReport = jest.fn();
const mockedRequiresSynchronization = jest.fn();
const mockedValidateSnapshot = jest.fn();
const mockedPreviewSnapshot = jest.fn();
const mockedLastReport = jest.fn();

Expand Down Expand Up @@ -59,10 +60,10 @@ const mockConfig = () => {
);
};

const mockSnapshotFactory = async (validResponse: unknown) => {
const mockSnapshotFactory = async () => {
mockedSnapshotFactory.createFromZip.mockReturnValue(
Promise.resolve({
validate: () => Promise.resolve(validResponse),
validate: mockedValidateSnapshot,
preview: mockedPreviewSnapshot,
delete: mockedDeleteSnapshot,
saveDetailedReport: mockedSaveDetailedReport,
Expand All @@ -75,11 +76,13 @@ const mockSnapshotFactory = async (validResponse: unknown) => {
};

const mockSnapshotFactoryReturningValidSnapshot = async () => {
await mockSnapshotFactory({isValid: true, report: {}});
mockedValidateSnapshot.mockResolvedValue({isValid: true, report: {}});
await mockSnapshotFactory();
};

const mockSnapshotFactoryReturningInvalidSnapshot = async () => {
await mockSnapshotFactory({isValid: false, report: {}});
mockedValidateSnapshot.mockResolvedValue({isValid: false, report: {}});
await mockSnapshotFactory();
};

describe('org:config:preview', () => {
Expand Down Expand Up @@ -127,6 +130,18 @@ describe('org:config:preview', () => {
);
});

test
.command(['org:config:preview'])
.it('#validate should not take into account missing resources', () => {
expect(mockedValidateSnapshot).toHaveBeenCalledWith(false);
});

test
.command(['org:config:preview', '-d'])
.it('#validate should take into account missing resoucres', () => {
expect(mockedValidateSnapshot).toHaveBeenCalledWith(true);
});

test
.command(['org:config:preview'])
.it('should preview the snapshot', () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/commands/org/config/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export default class Preview extends Command {
default: cwd(),
required: false,
}),
showResourcesToDelete: flags.boolean({
char: 'd',
description: 'Whether or not to show resources to delete',
default: false,
required: false,
}),
};

public static hidden = true;
Expand All @@ -54,7 +60,7 @@ export default class Preview extends Command {

cli.action.start('Validating snapshot');

const {isValid} = await snapshot.validate();
const {isValid} = await snapshot.validate(flags.showResourcesToDelete);

if (!isValid) {
await this.handleInvalidSnapshot(snapshot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,18 @@ describe('ReportViewer', () => {
.it('should print resource changes', (ctx) => {
// Remove padding added by cli-ux so we can test the text and not the padding on the line
const trimedStdout = ctx.stdout
.split('\n')
.split(/$/m)
.map((s) => s.trimEnd())
.join('\n');
.join('');

expect(trimedStdout).toContain(dedent`
Previewing resource changes:
Extensions
+ 1 to create
- 2 to delete
Fields
~ 1 to update`);
~ 1 to update
`);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,35 @@ import {
ResourceSnapshotsReportResultCode,
} from '@coveord/platform-client';
import {cli} from 'cli-ux';
import {bgHex, green, yellow, red, bold, italic} from 'chalk';
import {red, italic} from 'chalk';
import {ReportViewerSection} from './reportViewerSection';
import {ReportViewerStyles} from './reportViewerStyles';
import {
ReportViewerOperationName,
ReportViewerResourceReportModel,
} from './reportViewerDataModels';

export class ReportViewer {
public static defaultOperationsToDisplay: ReportViewerOperationName[] = [
'resourcesCreated',
'resourcesDeleted',
'resourcesInError',
'resourcesRecreated',
'resourcesUnchanged',
'resourcesUpdated',
];

public static maximumNumberOfErrorsToPrint = 5;
public static styles = {
green: (txt: string) => green(txt),
yellow: (txt: string) => yellow(txt),
red: (txt: string) => red(txt),
header: (txt: string) => bold.hex('#1CEBCF')(txt),
error: (txt: string) => bgHex('#F64D64').hex('#272C3A')(txt),
};

public constructor(private readonly report: ResourceSnapshotsReportModel) {}
private operationsToDisplay: ReportViewerOperationName[];

public constructor(
private readonly report: ResourceSnapshotsReportModel,
operationsToDisplay: ReportViewerOperationName[] = []
) {
this.operationsToDisplay =
ReportViewer.defaultOperationsToDisplay.concat(operationsToDisplay);
}

public display(): void {
this.printTable();
Expand All @@ -28,84 +44,44 @@ export class ReportViewer {

private printTable() {
if (this.changedResources.length === 0) {
cli.log(ReportViewer.styles.header('\nNo changes detected'));
cli.log(ReportViewerStyles.header('\nNo changes detected'));
return;
}

cli.table(this.changedResources, {
resourceName: {
header: ReportViewer.styles.header('\nPreviewing resource changes:'),
get: (row) => this.printTableSection(row),
header: ReportViewerStyles.header('\nPreviewing resource changes:'),
get: (resource) => this.createSection(resource),
},
});
}

// TODO: Change logic once SRC-4448 is complete
private printTableSection(row: {
resourceName: string;
operations: ResourceSnapshotsReportOperationModel;
}) {
const resourceType = this.prettyPrintResourceName(row.resourceName);
let output = ` ${resourceType}\n`;

if (row.operations.resourcesCreated > 0) {
output += `${ReportViewer.styles.green(
'+'
)} ${ReportViewer.styles.green(
`${row.operations.resourcesCreated} to create`
)}\n`;
}
if (row.operations.resourcesRecreated > 0) {
output += `${ReportViewer.styles.yellow(
'+-'
)} ${ReportViewer.styles.yellow(
`${row.operations.resourcesCreated} to replace`
)}\n`;
}
if (row.operations.resourcesUpdated > 0) {
output += `${ReportViewer.styles.yellow(
'~'
)} ${ReportViewer.styles.yellow(
`${row.operations.resourcesUpdated} to update`
)}\n`;
}
// TODO: CDX-361: Only show delete items if delete flag is set to true
if (row.operations.resourcesDeleted > 0) {
output += `${ReportViewer.styles.red('-')} ${ReportViewer.styles.red(
`${row.operations.resourcesDeleted} to delete`
)}\n`;
}
if (row.operations.resourcesInError > 0) {
output += `${ReportViewer.styles.error(
`! ${row.operations.resourcesInError} in error `
)}\n`;
}
private createSection(resource: ReportViewerResourceReportModel) {
const resourceType = this.prettyPrintResourceName(resource.name);
const indentation = 3;
const section = new ReportViewerSection(resource, this.operationsToDisplay);
let output = `${''.padStart(indentation)}${resourceType}\n`;

output += section.display(indentation + 1);
return output;
}

private get changedResources() {
private get changedResources(): ReportViewerResourceReportModel[] {
type resourceEntries = [string, ResourceSnapshotsReportOperationModel];
const resourceHasAtLeastOneOperation = ([
_,
operations,
]: resourceEntries) => {
return (
operations.resourcesCreated +
operations.resourcesUpdated +
operations.resourcesRecreated +
// TODO: CDX-361: Only count delete items if delete flag is set to true
operations.resourcesDeleted +
operations.resourcesInError >
0
this.operationsToDisplay.reduce(
(previous, current) => previous + operations[current],
0
) > 0
);
};

const convertArrayToObject = ([
resourceName,
operations,
]: resourceEntries) => ({
resourceName,
const convertArrayToObject = ([name, operations]: resourceEntries) => ({
name,
operations,
});

Expand Down Expand Up @@ -139,9 +115,9 @@ export class ReportViewer {
private handleReportErrors() {
const totalErrorCount = this.getOperationTypeTotalCount('resourcesInError');

cli.log(ReportViewer.styles.header('Error Report:'));
cli.log(ReportViewerStyles.header('Error Report:'));
cli.log(
ReportViewer.styles.error(
ReportViewerStyles.error(
` ${totalErrorCount} resource${
totalErrorCount > 1 ? 's' : ''
} in error `
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {ResourceSnapshotsReportOperationModel} from '@coveord/platform-client';

export type ReportViewerOperationName =
keyof ResourceSnapshotsReportOperationModel;

export interface ReportViewerResourceReportModel {
name: string;
operations: ResourceSnapshotsReportOperationModel;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import stripAnsi from 'strip-ansi';
import dedent from 'ts-dedent';
import {
ReportViewerOperationName,
ReportViewerResourceReportModel,
} from './reportViewerDataModels';
import {ReportViewerSection} from './reportViewerSection';

describe('ReportViewerSection', () => {
const resourceWithChanges: ReportViewerResourceReportModel = {
name: 'FOO_RESOURCE',
operations: {
resourcesCreated: 10,
resourcesDeleted: 2,
resourcesInError: 4,
resourcesRecreated: 0,
resourcesUnchanged: 5,
resourcesUpdated: 0,
},
};

const unsyncedResource: ReportViewerResourceReportModel = {
name: 'UNSYNCED_RESOURCE',
operations: {
resourcesCreated: 0,
resourcesDeleted: 0,
resourcesInError: 0,
resourcesRecreated: 0,
resourcesUnchanged: 0,
resourcesUpdated: 0,
},
};

const allOperationsAllowed: ReportViewerOperationName[] = [
'resourcesCreated',
'resourcesDeleted',
'resourcesInError',
'resourcesRecreated',
'resourcesUnchanged',
'resourcesUpdated',
];

describe('when there are no resource changes', () => {
it('should print nothing', () => {
const section = new ReportViewerSection(
unsyncedResource,
allOperationsAllowed
);

expect(section.display(0)).toEqual('');
});
});

describe('when there are resource changes', () => {
const section = new ReportViewerSection(
resourceWithChanges,
allOperationsAllowed
);

it('should print all operations with changes', () => {
expect(stripAnsi(section.display(0))).toContain(dedent`
+10 to create
-2 to delete
!4 in error
5 unchanged
`);
});

it('should indent with right amount of spaces', () => {
expect(stripAnsi(section.display(3))).toContain(dedent`
+ 10 to create
- 2 to delete
! 4 in error
5 unchanged
`);
});
});

describe('when only a subset of operations are allowed ', () => {
const section = new ReportViewerSection(resourceWithChanges, [
'resourcesDeleted',
]);

it('should print allowed operation', () => {
expect(stripAnsi(section.display(0))).toContain(dedent`
-2 to delete
`);
});
});
});
Loading

0 comments on commit e0c9aad

Please sign in to comment.