From e0c9aade588838e3636378bfb6cf017557663365 Mon Sep 17 00:00:00 2001 From: Yassine Date: Thu, 17 Jun 2021 10:08:24 -0400 Subject: [PATCH] feat: add flag to support delete changes (#258) https://coveord.atlassian.net/browse/CDX-361 --- packages/cli/package-lock.json | 246 ++++++++++++++++++ packages/cli/package.json | 1 + .../src/commands/org/config/preview.spec.ts | 23 +- .../cli/src/commands/org/config/preview.ts | 8 +- .../{ => reportViewer}/reportViewer.spec.ts | 8 +- .../{ => reportViewer}/reportViewer.ts | 110 +++----- .../reportViewer/reportViewerDataModels.ts | 9 + .../reportViewer/reportViewerSection.spec.ts | 90 +++++++ .../reportViewer/reportViewerSection.ts | 86 ++++++ .../reportViewer/reportViewerStyles.ts | 10 + packages/cli/src/lib/snapshot/snapshot.ts | 8 +- 11 files changed, 520 insertions(+), 79 deletions(-) rename packages/cli/src/lib/snapshot/{ => reportViewer}/reportViewer.spec.ts (98%) rename packages/cli/src/lib/snapshot/{ => reportViewer}/reportViewer.ts (53%) create mode 100644 packages/cli/src/lib/snapshot/reportViewer/reportViewerDataModels.ts create mode 100644 packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.spec.ts create mode 100644 packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.ts create mode 100644 packages/cli/src/lib/snapshot/reportViewer/reportViewerStyles.ts diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json index ec2db0119f..7778713672 100644 --- a/packages/cli/package-lock.json +++ b/packages/cli/package-lock.json @@ -1601,6 +1601,17 @@ "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "@jest/environment": { @@ -2199,6 +2210,14 @@ "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } } } }, @@ -2322,6 +2341,21 @@ "strip-ansi": "^6.0.0" } }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + } + } + }, "wrap-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz", @@ -3427,6 +3461,14 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -3590,6 +3632,21 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4859,6 +4916,16 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "type-fest": { @@ -5234,6 +5301,16 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } } } } @@ -5319,6 +5396,14 @@ "strip-ansi": "^6.0.0" } }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, "supports-hyperlinks": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", @@ -5375,6 +5460,14 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } } } }, @@ -6577,6 +6670,15 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -8227,6 +8329,14 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } } } }, @@ -8667,6 +8777,17 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "find-up": { @@ -8739,6 +8860,17 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "wrap-ansi": { @@ -8750,6 +8882,17 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "y18n": { @@ -9182,6 +9325,17 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "find-up": { @@ -9233,6 +9387,17 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "wrap-ansi": { @@ -9244,6 +9409,17 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "y18n": { @@ -10827,6 +11003,16 @@ "log-symbols": "^4.0.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "os-tmpdir": { @@ -12908,6 +13094,17 @@ "requires": { "debug": "^4.1.1", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "stealthy-require": { @@ -12942,6 +13139,17 @@ "requires": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } } }, "string-width": { @@ -12999,6 +13207,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -13166,6 +13375,15 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } } } }, @@ -14197,6 +14415,16 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } } } } @@ -14231,6 +14459,14 @@ "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } } } }, @@ -14408,6 +14644,16 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } } } } diff --git a/packages/cli/package.json b/packages/cli/package.json index b959084179..7d5009dabd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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" diff --git a/packages/cli/src/commands/org/config/preview.spec.ts b/packages/cli/src/commands/org/config/preview.spec.ts index dfec36e0ab..c7068d97db 100644 --- a/packages/cli/src/commands/org/config/preview.spec.ts +++ b/packages/cli/src/commands/org/config/preview.spec.ts @@ -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(); @@ -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, @@ -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', () => { @@ -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', () => { diff --git a/packages/cli/src/commands/org/config/preview.ts b/packages/cli/src/commands/org/config/preview.ts index 829fd2b17d..92bdb6f7df 100644 --- a/packages/cli/src/commands/org/config/preview.ts +++ b/packages/cli/src/commands/org/config/preview.ts @@ -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; @@ -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); diff --git a/packages/cli/src/lib/snapshot/reportViewer.spec.ts b/packages/cli/src/lib/snapshot/reportViewer/reportViewer.spec.ts similarity index 98% rename from packages/cli/src/lib/snapshot/reportViewer.spec.ts rename to packages/cli/src/lib/snapshot/reportViewer/reportViewer.spec.ts index 94ed061d18..c89816b7f9 100644 --- a/packages/cli/src/lib/snapshot/reportViewer.spec.ts +++ b/packages/cli/src/lib/snapshot/reportViewer/reportViewer.spec.ts @@ -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 + `); }); }); diff --git a/packages/cli/src/lib/snapshot/reportViewer.ts b/packages/cli/src/lib/snapshot/reportViewer/reportViewer.ts similarity index 53% rename from packages/cli/src/lib/snapshot/reportViewer.ts rename to packages/cli/src/lib/snapshot/reportViewer/reportViewer.ts index 3ffd3e459a..d84c247048 100644 --- a/packages/cli/src/lib/snapshot/reportViewer.ts +++ b/packages/cli/src/lib/snapshot/reportViewer/reportViewer.ts @@ -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(); @@ -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, }); @@ -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 ` diff --git a/packages/cli/src/lib/snapshot/reportViewer/reportViewerDataModels.ts b/packages/cli/src/lib/snapshot/reportViewer/reportViewerDataModels.ts new file mode 100644 index 0000000000..e245f69880 --- /dev/null +++ b/packages/cli/src/lib/snapshot/reportViewer/reportViewerDataModels.ts @@ -0,0 +1,9 @@ +import {ResourceSnapshotsReportOperationModel} from '@coveord/platform-client'; + +export type ReportViewerOperationName = + keyof ResourceSnapshotsReportOperationModel; + +export interface ReportViewerResourceReportModel { + name: string; + operations: ResourceSnapshotsReportOperationModel; +} diff --git a/packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.spec.ts b/packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.spec.ts new file mode 100644 index 0000000000..cc511acb72 --- /dev/null +++ b/packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.spec.ts @@ -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 + `); + }); + }); +}); diff --git a/packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.ts b/packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.ts new file mode 100644 index 0000000000..080a0ced04 --- /dev/null +++ b/packages/cli/src/lib/snapshot/reportViewer/reportViewerSection.ts @@ -0,0 +1,86 @@ +import {ReportViewerStyles} from './reportViewerStyles'; +import { + ReportViewerOperationName, + ReportViewerResourceReportModel, +} from './reportViewerDataModels'; +import {ResourceSnapshotsReportOperationModel} from '@coveord/platform-client'; + +class ReportViewerOperationLogFactory { + public constructor( + private operator: string, + private color: keyof typeof ReportViewerStyles, + private templateString: (count: number) => string + ) {} + + public getString(count: number, indentation: number) { + return `${ReportViewerStyles[this.color]( + this.operator.padEnd(indentation) + )}${ReportViewerStyles[this.color](this.templateString(count))}\n`; + } +} + +export class ReportViewerSection { + public constructor( + private resource: ReportViewerResourceReportModel, + private operationsToDisplay: ReportViewerOperationName[] + ) {} + + public static operationToLogFactoryDictionnary: { + [key in ReportViewerOperationName]: ReportViewerOperationLogFactory; + } = { + resourcesCreated: new ReportViewerOperationLogFactory( + '+', + 'green', + (count) => `${count} to create` + ), + resourcesRecreated: new ReportViewerOperationLogFactory( + '+-', + 'yellow', + (count) => `${count} to replace` + ), + resourcesUpdated: new ReportViewerOperationLogFactory( + '~', + 'yellow', + (count) => `${count} to update` + ), + resourcesDeleted: new ReportViewerOperationLogFactory( + '-', + 'red', + (count) => `${count} to delete` + ), + resourcesInError: new ReportViewerOperationLogFactory( + '!', + 'error', + (count) => `${count} in error` + ), + resourcesUnchanged: new ReportViewerOperationLogFactory( + ' ', + 'gray', + (count) => `${count} unchanged` + ), + }; + + private shouldPrintOperation( + operation: keyof ResourceSnapshotsReportOperationModel + ): boolean { + const canBeDisplayed = this.operationsToDisplay.includes(operation); + const isResourceAffectedByOperation = + this.resource.operations[operation] > 0; + + return canBeDisplayed && isResourceAffectedByOperation; + } + + public display(indentation: number) { + return this.operationsToDisplay.reduce((previous, current) => { + if (!this.shouldPrintOperation(current)) { + return previous; + } + const operationToLog = + ReportViewerSection.operationToLogFactoryDictionnary[current].getString( + this.resource.operations[current], + indentation + ); + return previous + operationToLog; + }, ''); + } +} diff --git a/packages/cli/src/lib/snapshot/reportViewer/reportViewerStyles.ts b/packages/cli/src/lib/snapshot/reportViewer/reportViewerStyles.ts new file mode 100644 index 0000000000..24eaff5a3c --- /dev/null +++ b/packages/cli/src/lib/snapshot/reportViewer/reportViewerStyles.ts @@ -0,0 +1,10 @@ +import {bgHex, green, yellow, red, bold, gray} from 'chalk'; + +export const ReportViewerStyles = { + green: (txt: string) => green(txt), + yellow: (txt: string) => yellow(txt), + red: (txt: string) => red(txt), + gray: (txt: string) => gray(txt), + header: (txt: string) => bold.hex('#1CEBCF')(txt), + error: (txt: string) => bgHex('#F64D64').hex('#272C3A')(txt), +}; diff --git a/packages/cli/src/lib/snapshot/snapshot.ts b/packages/cli/src/lib/snapshot/snapshot.ts index 30e9cf0eca..5335f40b87 100644 --- a/packages/cli/src/lib/snapshot/snapshot.ts +++ b/packages/cli/src/lib/snapshot/snapshot.ts @@ -8,7 +8,7 @@ import { } from '@coveord/platform-client'; import {cli} from 'cli-ux'; import {backOff} from 'exponential-backoff'; -import {ReportViewer} from './reportViewer'; +import {ReportViewer} from './reportViewer/reportViewer'; import {ensureFileSync, writeJsonSync} from 'fs-extra'; import {join} from 'path'; import dedent from 'ts-dedent'; @@ -29,9 +29,11 @@ export class Snapshot { private client: PlatformClient ) {} - public async validate(): Promise { + public async validate( + deleteMissingResources = false + ): Promise { await this.snapshotClient.dryRun(this.id, { - deleteMissingResources: false, // TODO: CDX-361: Add flag to support missing resources deletion + deleteMissingResources, }); await this.waitUntilOperationIsDone(ResourceSnapshotsReportType.DryRun);