From 3477680dd2bf74ac021d74a6488e6e6fa770cc80 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Thu, 16 May 2019 15:19:54 -0700 Subject: [PATCH] Added a help button to the sam init wizard's runtime selection step (#538) --- resources/dark/help.svg | 10 ++++ resources/light/help.svg | 10 ++++ src/lambda/commands/createNewSamApp.ts | 7 +-- src/lambda/lambdaTreeDataProvider.ts | 2 +- src/lambda/wizards/samInitWizard.ts | 36 +++++++++++-- src/shared/constants.ts | 3 ++ src/shared/ui/buttons.ts | 31 +++++++++++ src/test/fakeExtensionContext.ts | 2 +- src/test/lambda/wizards/samInitWizard.test.ts | 54 ++++++++++--------- src/test/shared/ui/buttons.test.ts | 32 +++++++++++ 10 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 resources/dark/help.svg create mode 100644 resources/light/help.svg create mode 100644 src/shared/ui/buttons.ts create mode 100644 src/test/shared/ui/buttons.test.ts diff --git a/resources/dark/help.svg b/resources/dark/help.svg new file mode 100644 index 00000000000..25a9db0bfbb --- /dev/null +++ b/resources/dark/help.svg @@ -0,0 +1,10 @@ + + + + Artboard Copy 15 + Created with Sketch. + + + + + \ No newline at end of file diff --git a/resources/light/help.svg b/resources/light/help.svg new file mode 100644 index 00000000000..3092aed1a42 --- /dev/null +++ b/resources/light/help.svg @@ -0,0 +1,10 @@ + + + + Artboard Copy 17 + Created with Sketch. + + + + + \ No newline at end of file diff --git a/src/lambda/commands/createNewSamApp.ts b/src/lambda/commands/createNewSamApp.ts index 6950d54799f..b987ca30e22 100644 --- a/src/lambda/commands/createNewSamApp.ts +++ b/src/lambda/commands/createNewSamApp.ts @@ -12,7 +12,7 @@ import * as path from 'path' import * as vscode from 'vscode' import { SamCliInitArgs, SamCliInitInvocation } from '../../shared/sam/cli/samCliInit' import { getMainSourceFileUri } from '../utilities/getMainSourceFile' -import { CreateNewSamAppWizard } from '../wizards/samInitWizard' +import { CreateNewSamAppWizard, DefaultCreateNewSamAppWizardContext } from '../wizards/samInitWizard' export const URI_TO_OPEN_ON_INIT_KEY = 'URI_TO_OPEN_ON_INIT_KEY' @@ -48,9 +48,10 @@ interface NewSamAppMetadata { * Runs `sam init` in the given context and returns useful metadata about its invocation */ export async function createNewSamApp( - context: Pick + context: Pick ): Promise { - const config = await new CreateNewSamAppWizard().run() + const wizardContext = new DefaultCreateNewSamAppWizardContext(context) + const config = await new CreateNewSamAppWizard(wizardContext).run() if (!config) { return undefined } diff --git a/src/lambda/lambdaTreeDataProvider.ts b/src/lambda/lambdaTreeDataProvider.ts index 673385d8cea..95e0bfc66b7 100644 --- a/src/lambda/lambdaTreeDataProvider.ts +++ b/src/lambda/lambdaTreeDataProvider.ts @@ -51,7 +51,7 @@ export class LambdaTreeDataProvider implements vscode.TreeDataProvider() } - public initialize(context: Pick): void { + public initialize(context: Pick): void { registerCommand({ command: 'aws.refreshAwsExplorer', callback: async () => this.refresh() diff --git a/src/lambda/wizards/samInitWizard.ts b/src/lambda/wizards/samInitWizard.ts index 21df4193ae2..6d967272d0c 100644 --- a/src/lambda/wizards/samInitWizard.ts +++ b/src/lambda/wizards/samInitWizard.ts @@ -12,7 +12,9 @@ import * as immutable from 'immutable' import * as os from 'os' import * as path from 'path' import * as vscode from 'vscode' +import { samInitDocUrl } from '../../shared/constants' import { SamCliInitArgs } from '../../shared/sam/cli/samCliInit' +import { createHelpButton } from '../../shared/ui/buttons' import * as input from '../../shared/ui/input' import * as picker from '../../shared/ui/picker' import * as lambdaRuntime from '../models/samLambdaRuntime' @@ -20,6 +22,7 @@ import { MultiStepWizard, WizardStep } from '../wizards/multiStepWizard' export interface CreateNewSamAppWizardContext { readonly lambdaRuntimes: immutable.Set readonly workspaceFolders: vscode.WorkspaceFolder[] | undefined + readonly extContext: Pick promptUserForRuntime( currRuntime?: lambdaRuntime.SamLambdaRuntime @@ -34,9 +37,19 @@ export interface CreateNewSamAppWizardContext { ): Thenable } -class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContext { +export class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContext { public readonly lambdaRuntimes = lambdaRuntime.samLambdaRuntimes public readonly showOpenDialog = vscode.window.showOpenDialog + private readonly helpButton = createHelpButton( + this.extContext, + localize( + 'AWS.command.help', + 'View Documentation' + ) + ) + + public constructor(readonly extContext: Pick) { + } public get workspaceFolders(): vscode.WorkspaceFolder[] | undefined { return vscode.workspace.workspaceFolders @@ -54,6 +67,10 @@ class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContex ), value: currRuntime ? currRuntime : '' }, + buttons: [ + this.helpButton, + vscode.QuickInputButtons.Back + ], items: this.lambdaRuntimes .toArray() .sort() @@ -66,7 +83,14 @@ class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContex }) const choices = await picker.promptUser({ - picker: quickPick + picker: quickPick, + onDidTriggerButton: (button, resolve, reject) => { + if (button === vscode.QuickInputButtons.Back) { + resolve(undefined) + } else if (button === this.helpButton) { + vscode.env.openExternal(vscode.Uri.parse(samInitDocUrl)) + } + } }) const val = picker.verifySinglePickerOutput(choices) @@ -88,6 +112,7 @@ class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContex }, items: items, buttons: [ + this.helpButton, vscode.QuickInputButtons.Back ] }) @@ -97,6 +122,8 @@ class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContex onDidTriggerButton: (button, resolve, reject) => { if (button === vscode.QuickInputButtons.Back) { resolve(undefined) + } else if (button === this.helpButton) { + vscode.env.openExternal(vscode.Uri.parse(samInitDocUrl)) } } }) @@ -115,6 +142,7 @@ class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContex ignoreFocusOut: true, }, buttons: [ + this.helpButton, vscode.QuickInputButtons.Back ] }) @@ -142,6 +170,8 @@ class DefaultCreateNewSamAppWizardContext implements CreateNewSamAppWizardContex onDidTriggerButton: (button, resolve, reject) => { if (button === vscode.QuickInputButtons.Back) { resolve(undefined) + } else if (button === this.helpButton) { + vscode.env.openExternal(vscode.Uri.parse(samInitDocUrl)) } } }) @@ -154,7 +184,7 @@ export class CreateNewSamAppWizard extends MultiStepWizard { private name?: string public constructor( - private readonly context: CreateNewSamAppWizardContext = new DefaultCreateNewSamAppWizardContext() + private readonly context: CreateNewSamAppWizardContext ) { super() } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 30c0b83830a..04567e1ba1b 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -20,5 +20,8 @@ export const vscodeMarketplaceUrl: string = 'https://marketplace.visualstudio.co export const githubUrl: string = 'https://github.com/aws/aws-toolkit-vscode' export const documentationUrl: string = 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html' +// URLs for samInitWizard +export const samInitDocUrl: string = 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/create-sam.html' + const npmPackage = () => require('../../../package.json') as NpmPackage export const pluginVersion = npmPackage().version diff --git a/src/shared/ui/buttons.ts b/src/shared/ui/buttons.ts new file mode 100644 index 00000000000..99fa4354239 --- /dev/null +++ b/src/shared/ui/buttons.ts @@ -0,0 +1,31 @@ +/*! + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +import * as path from 'path' +import { ExtensionContext, QuickInputButton, Uri } from 'vscode' + +/** + * Creates a QuickInputButton with a predefined help button (dark and light theme compatible) + * Images are only loaded after extension.ts loads; this should happen on any user-facing extension usage. + * button will exist regardless of image loading (UI tests will still see this) + * @param tooltip Optional tooltip for button + */ +export function createHelpButton( + context: Pick, + tooltip?: string +): QuickInputButton { + const light = path.join(context.asAbsolutePath('resources'), 'light', 'help.svg') + const dark = path.join(context.asAbsolutePath('resources'), 'dark', 'help.svg') + + return { + iconPath: { + light: Uri.file(light), + dark: Uri.file(dark) + }, + tooltip + } +} diff --git a/src/test/fakeExtensionContext.ts b/src/test/fakeExtensionContext.ts index 9def22165c2..e1ee7e31d5c 100644 --- a/src/test/fakeExtensionContext.ts +++ b/src/test/fakeExtensionContext.ts @@ -35,7 +35,7 @@ export class FakeExtensionContext implements ExtensionContext { } public asAbsolutePath(relativePath: string): string { - throw new Error('Method not implemented.') + return relativePath } } diff --git a/src/test/lambda/wizards/samInitWizard.test.ts b/src/test/lambda/wizards/samInitWizard.test.ts index 9206ac0fc7d..113251c00e3 100644 --- a/src/test/lambda/wizards/samInitWizard.test.ts +++ b/src/test/lambda/wizards/samInitWizard.test.ts @@ -14,6 +14,7 @@ import { CreateNewSamAppWizard, CreateNewSamAppWizardContext } from '../../../lambda/wizards/samInitWizard' +import { FakeExtensionContext } from '../../fakeExtensionContext' function isMultiDimensionalArray(array: any[] | any[][] | undefined): boolean { if (!array) { @@ -30,6 +31,34 @@ function isMultiDimensionalArray(array: any[] | any[][] | undefined): boolean { } class MockCreateNewSamAppWizardContext implements CreateNewSamAppWizardContext { + + public get lambdaRuntimes(): immutable.Set { + if (Array.isArray(this._lambdaRuntimes)) { + if (this._lambdaRuntimes!.length <= 0) { + throw new Error('lambdaRuntimes was called more times than expected') + } + + return (this._lambdaRuntimes as immutable.Set[]).pop() || immutable.Set() + } + + return (this._lambdaRuntimes as immutable.Set) || immutable.Set() + } + + public get workspaceFolders(): vscode.WorkspaceFolder[] { + if (isMultiDimensionalArray(this._workspaceFolders)) { + if (this._workspaceFolders!.length <= 0) { + throw new Error('workspaceFolders was called more times than expected') + } + + return (this._workspaceFolders as vscode.WorkspaceFolder[][]).pop() || [] + } + + return (this._workspaceFolders as vscode.WorkspaceFolder[]) || [] + + } + + public readonly extContext = new FakeExtensionContext() + /** * @param {vscode.WorkspaceFolder[] | vscode.WorkspaceFolder[][]} _workspaceFolders * The value to return from context.workspaceFolders. @@ -63,31 +92,6 @@ class MockCreateNewSamAppWizardContext implements CreateNewSamAppWizardContext { } } - public get lambdaRuntimes(): immutable.Set { - if (Array.isArray(this._lambdaRuntimes)) { - if (this._lambdaRuntimes!.length <= 0) { - throw new Error('lambdaRuntimes was called more times than expected') - } - - return (this._lambdaRuntimes as immutable.Set[]).pop() || immutable.Set() - } - - return (this._lambdaRuntimes as immutable.Set) || immutable.Set() - } - - public get workspaceFolders(): vscode.WorkspaceFolder[] { - if (isMultiDimensionalArray(this._workspaceFolders)) { - if (this._workspaceFolders!.length <= 0) { - throw new Error('workspaceFolders was called more times than expected') - } - - return (this._workspaceFolders as vscode.WorkspaceFolder[][]).pop() || [] - } - - return (this._workspaceFolders as vscode.WorkspaceFolder[]) || [] - - } - public async showOpenDialog( options: vscode.OpenDialogOptions ): Promise { diff --git a/src/test/shared/ui/buttons.test.ts b/src/test/shared/ui/buttons.test.ts new file mode 100644 index 00000000000..9adb6d2425d --- /dev/null +++ b/src/test/shared/ui/buttons.test.ts @@ -0,0 +1,32 @@ +/*! + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +import * as assert from 'assert' +import * as vscode from 'vscode' +import * as buttons from '../../../shared/ui/buttons' +import { FakeExtensionContext } from '../../fakeExtensionContext' + +describe('UI buttons', () => { + + const extContext = new FakeExtensionContext() + + it('creates a help button without a tooltip or icons', () => { + const help = buttons.createHelpButton(extContext) + const paths = help.iconPath as {light: vscode.Uri, dark: vscode.Uri} + + assert.strictEqual(help.tooltip, undefined) + assert.ok(paths.light) + assert.ok(paths.dark) + }) + + it('creates a help button with a tooltip', () => { + const tooltip = 'you must be truly desperate to come to me for help' + const help = buttons.createHelpButton(extContext, tooltip) + + assert.strictEqual(help.tooltip, tooltip) + }) +})