Skip to content

Commit

Permalink
Feature/amplify enhanced status (aws-amplify#7698)
Browse files Browse the repository at this point in the history
* enhanced amplify status

* help and test

* show summary view on amplify push

* Fixed s3

* unit test for showStatusTable

* fixed ViewResourceTableParams

* Converted cloudformation file-path to use Glob

* cleanup and headers

* 1. removed custom yaml handling and used library code.
2. Used readCFNTemplate to load templates.
3. Cleaned up unused code

* addressed PR comments

* Added types to template, diff

* addressed more PR comments

* updated unit test coverage and fixed test errors

* fix multi-env diffs for new resources

* 1. category filters for summary.
2. removed spacing between categories in detail-view.
3. updated help for summary.
4. added case normalization for options

* updated help to indicate summary filters

* Update Readme.md

Added category filters for summary table

* fixed unit tests and merge errors

* Update Readme.md

fixed case

* Update packages/amplify-cli-core/src/cliViewAPI.ts

Co-authored-by: akshbhu <39866697+akshbhu@users.noreply.github.com>

* Update packages/amplify-cli/src/extensions/amplify-helpers/resource-status-view.ts

Co-authored-by: akshbhu <39866697+akshbhu@users.noreply.github.com>

* addressed CR comments

* lgtm:fix:removed unused variable

* unit-tests for cliViewAPI

* removed styling from help test

* unit-test for detailed cloudformation-diff for one resource

Co-authored-by: Sachin Panemangalore <sachinrp@amazon.com>
Co-authored-by: akshbhu <39866697+akshbhu@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 15, 2021
1 parent e1d80b8 commit a2ae445
Show file tree
Hide file tree
Showing 31 changed files with 7,820 additions and 162 deletions.
3 changes: 2 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ The Amplify CLI supports the commands shown in the following table.
| amplify push [--no-gql-override] | Provisions cloud resources with the latest local developments. The 'no-gql-override' flag does not automatically compile your annotated GraphQL schema and will override your local AppSync resolvers and templates. |
| amplify pull | Fetch upstream backend environment definition changes from the cloud and updates the local environment to match that definition. |
| amplify publish | Runs `amplify push`, publishes a static assets to Amazon S3 and Amazon CloudFront (\*hosting category is required). |
| amplify status | Displays the state of local resources that haven't been pushed to the cloud (Create/Update/Delete). |
| amplify status [ `<category>`...] | Displays the state of local resources that haven't been pushed to the cloud (Create/Update/Delete). |
| amplify status -v [ `<category>`...] | Verbose mode - Shows the detailed verbose diff between local and deployed resources, including cloudformation-diff |
| amplify serve | Runs `amplify push`, and then executes the project's start command to test run the client-side application. |
| amplify delete | Deletes resources tied to the project. |
| amplify help \| amplify `<category>` help | Displays help for the core CLI. |
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"author": "Amazon Web Services",
"license": "Apache-2.0",
"dependencies": {
"strip-ansi": "^6.0.0",
"lerna": "^4.0.0"
},
"workspaces": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`CLI View tests Status Help CLI should correctly return styled help message 1`] = `
"
NAME
amplify status -- Shows the state of local resources not yet pushed to the cloud (Create/Update/Delete)
SYNOPSIS
amplify status [-v|--verbose] [category ...]
DESCRIPTION
The amplify status command displays the difference between the deployed state and the local state of the application.
The following options are available:
[category ...] : (Summary mode) Displays the summary of local state vs deployed state of the application
usage:
#> amplify status
#> amplify status api storage
-v [category ...] : (Verbose mode) Displays the cloudformation diff for all resources for the specified category.
If no category is provided, it shows the diff for all categories.
usage:
#> amplify status -v
#> amplify status -v api storage
"
`;
65 changes: 65 additions & 0 deletions packages/amplify-cli-core/src/__tests__/cliViewAPI.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { CLIParams, ViewResourceTableParams } from '../cliViewAPI';
import chalk from 'chalk';
import stripAnsi from 'strip-ansi';

describe('CLI View tests', () => {
test('Verbose mode CLI status with category list should correctly initialize ViewResourceTableParams [Non-Help]', () => {
const cliParams : CLIParams = {
cliCommand: 'status',
cliSubcommands: undefined,
cliOptions: {
storage: true,
api: true,
verbose: true,
yes: false
}
}
const view = new ViewResourceTableParams(cliParams);
expect( view.command ).toBe("status");
expect( view.categoryList).toStrictEqual(['storage', 'api']);
expect( view.help ).toBe(false);
expect( view.verbose ).toBe(true);
});

test('Status Help CLI should correctly return styled help message', () => {
const cliParams : CLIParams = {
cliCommand: 'status',
cliSubcommands: [ 'help' ],
cliOptions: { yes: false }
};

const view = new ViewResourceTableParams(cliParams);
expect( view.command ).toBe("status");
expect( view.categoryList).toStrictEqual([]);
expect( view.help ).toBe(true);
expect( view.verbose ).toBe(false);
const styledHelp = stripAnsi(chalk.reset(view.getStyledHelp()));
expect(styledHelp).toMatchSnapshot();
});

test('Status Command should print error message to the screen', () => {
const cliParams : CLIParams = {
cliCommand: 'status',
cliSubcommands: [ 'help' ],
cliOptions: { yes: false }
};
const view = new ViewResourceTableParams(cliParams);
const errorMockFn = jest.fn();

const context: any = {
print : {
error: errorMockFn
}
};
const errorMessage = "Something bad happened"
try {
throw new Error(errorMessage);
}
catch(e) {
view.logErrorException(e, context);
expect(errorMockFn).toBeCalledTimes(1);
}

});

} );
88 changes: 88 additions & 0 deletions packages/amplify-cli-core/src/cliViewAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//Use this file to store all types used between the CLI commands and the view/display functions
// CLI=>(command-handler)==[CLI-View-API]=>(ux-handler/report-handler)=>output-stream
import chalk from 'chalk';
import { $TSAny, $TSContext } from '.';
export interface CLIParams {
cliCommand: string;
cliSubcommands: string[] | undefined;
cliOptions: Record<string,$TSAny>;
}
//Resource Table filter and display params (params used for summary/display view of resource table)
export class ViewResourceTableParams {
private _command: string;
private _verbose: boolean; //display table in verbose mode
private _help: boolean; //display help for the command
private _categoryList: string[] | []; //categories to display
private _filteredResourceList: any; //resources to *not* display - TBD define union of valid types

public get command() {
return this._command;
}
public get verbose() {
return this._verbose;
}
public get help() {
return this._help;
}
public get categoryList() {
return this._categoryList;
}
getCategoryFromCLIOptions(cliOptions: object) {
if (cliOptions) {
return Object.keys(cliOptions)
.filter(key => key != 'verbose' && key !== 'yes')
.map(category => category.toLowerCase());
} else {
return [];
}
}
styleHeader(str: string) {
return chalk.italic(chalk.bgGray.whiteBright(str));
}
styleCommand(str: string) {
return chalk.greenBright(str);
}
styleOption(str: string) {
return chalk.yellowBright(str);
}
stylePrompt(str: string) {
return chalk.bold(chalk.yellowBright(str));
}
public getStyledHelp() {
return `
${this.styleHeader('NAME')}
${this.styleCommand('amplify status')} -- Shows the state of local resources not yet pushed to the cloud (Create/Update/Delete)
${this.styleHeader('SYNOPSIS')}
${this.styleCommand('amplify status')} [${this.styleCommand('-v')}|${this.styleCommand('--verbose')}] [${this.styleOption('category ...')}]
${this.styleHeader('DESCRIPTION')}
The amplify status command displays the difference between the deployed state and the local state of the application.
The following options are available:
${this.styleCommand('[category ...]')} : (Summary mode) Displays the summary of local state vs deployed state of the application
usage:
${this.stylePrompt('#>')} ${this.styleCommand('amplify status')}
${this.stylePrompt('#>')} ${this.styleCommand('amplify status')} ${this.styleOption('api storage')}
${this.styleCommand('-v [category ...]')} : (Verbose mode) Displays the cloudformation diff for all resources for the specified category.
If no category is provided, it shows the diff for all categories.
usage:
${this.stylePrompt('#>')} ${this.styleCommand('amplify status -v')}
${this.stylePrompt('#>')} ${this.styleCommand('amplify status -v ')}${this.styleOption('api storage')}
`;
}

public logErrorException( e : Error , context : $TSContext ){
context.print.error(`Name: ${e.name} : Message: ${e.message}`);
}

public constructor(cliParams: CLIParams) {
this._command = cliParams.cliCommand;
this._verbose = cliParams.cliOptions?.verbose === true;
this._categoryList = this.getCategoryFromCLIOptions(cliParams.cliOptions);
this._filteredResourceList = []; //TBD - add support to provide resources
this._help = cliParams.cliSubcommands ? cliParams.cliSubcommands.includes('help') : false;
}
}
6 changes: 5 additions & 1 deletion packages/amplify-cli-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ViewResourceTableParams } from './cliViewAPI';
import { ServiceSelection } from './serviceSelection';

export * from './cfnUtilities';
Expand All @@ -21,6 +22,7 @@ export * from './utils';
export * from './banner-message';
export * from './cliGetCategories';
export * from './cliRemoveResourcePrompt';
export * from "./cliViewAPI";

// Temporary types until we can finish full type definition across the whole CLI

Expand Down Expand Up @@ -200,6 +202,7 @@ export interface AmplifyProjectConfig {

export type $TSCopyJob = any;


// Temporary interface until Context refactor
interface AmplifyToolkit {
confirmPrompt: (prompt: string, defaultValue?: boolean) => Promise<boolean>;
Expand Down Expand Up @@ -253,8 +256,9 @@ interface AmplifyToolkit {
sharedQuestions: () => $TSAny;
showAllHelp: () => $TSAny;
showHelp: (header: string, commands: { name: string; description: string }[]) => $TSAny;
showHelpfulProviderLinks: () => $TSAny;
showHelpfulProviderLinks: (context : $TSContext) => $TSAny;
showResourceTable: () => $TSAny;
showStatusTable:( resourceTableParams : ViewResourceTableParams )=> $TSAny; //Enhanced Status with CFN-Diff
serviceSelectionPrompt: (
context: $TSContext,
category: string,
Expand Down
98 changes: 98 additions & 0 deletions packages/amplify-cli/src/__tests__/commands/status.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { UnknownArgumentError } from 'amplify-cli-core';

describe('amplify status: ', () => {
const { run } = require('../../commands/status');
const runStatusCmd = run;
const statusPluginInfo = `${process.cwd()}/../amplify-console-hosting`;
const mockPath = './';

it('status run method should exist', () => {
expect(runStatusCmd).toBeDefined();
});

it('status run method should call context.amplify.showStatusTable', async () => {
const cliInput = {
command: 'status',
subCommands: [],
options: {
verbose: true,
},
};

const mockContextNoCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: mockPath }),
},
parameters: {
input: {
command: 'status',
subCommands: [],
options: {
verbose: true,
},
},
},
};
runStatusCmd(mockContextNoCLArgs);
expect(mockContextNoCLArgs.amplify.showStatusTable).toBeCalled();
});

it('status -v run method should call context.amplify.showStatusTable', async () => {
const mockContextWithVerboseOptionAndCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }),
},
input: {
command: 'status',
options: {
verbose: true,
},
},
};
runStatusCmd(mockContextWithVerboseOptionAndCLArgs);
expect(mockContextWithVerboseOptionAndCLArgs.amplify.showStatusTable).toBeCalled();
});

it('status -v <category>* run method should call context.amplify.showStatusTable', async () => {
const mockContextWithVerboseOptionWithCategoriesAndCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }),
},
input: {
command: 'status',
options: {
verbose: true,
api: true,
storage: true,
},
},
};

runStatusCmd(mockContextWithVerboseOptionWithCategoriesAndCLArgs);
expect(mockContextWithVerboseOptionWithCategoriesAndCLArgs.amplify.showStatusTable).toBeCalled();
});

it('status help run method should call ViewResourceTableParams.getStyledHelp', async () => {
const mockContextWithHelpSubcommandAndCLArgs = {
amplify: {
showStatusTable: jest.fn(),
showHelpfulProviderLinks: jest.fn(),
getCategoryPluginInfo: jest.fn().mockReturnValue({ packageLocation: statusPluginInfo }),
},
input: {
command: 'status',
subCommands: ['help'],
},
};
runStatusCmd(mockContextWithHelpSubcommandAndCLArgs);
//TBD: to move ViewResourceTableParams into a separate file for mocking instance functions.
expect(mockContextWithHelpSubcommandAndCLArgs.amplify.showStatusTable.mock.calls.length).toBe(0);
});

});
Loading

0 comments on commit a2ae445

Please sign in to comment.