From c86211518e3af4d3878e9aeb62986b9c39976872 Mon Sep 17 00:00:00 2001 From: Eric Jizba Date: Thu, 21 Sep 2017 16:21:48 -0700 Subject: [PATCH] Add support for init function app and create function These are just simple wrappers for the cli at this point --- package.json | 34 ++++++++++++++++++++--- resources/dark/AddFunction.svg | 1 + resources/dark/InitFunctionApp.svg | 1 + resources/light/AddFunction.svg | 1 + resources/light/InitFunctionApp.svg | 1 + src/commands.ts | 30 +++++++++++++++++---- src/extension.ts | 22 +++++++++++---- src/functions-cli.ts | 26 ++++++++++++++++++ src/util.ts | 42 ++++++++++++++++++++++++++++- 9 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 resources/dark/AddFunction.svg create mode 100644 resources/dark/InitFunctionApp.svg create mode 100644 resources/light/AddFunction.svg create mode 100644 resources/light/InitFunctionApp.svg create mode 100644 src/functions-cli.ts diff --git a/package.json b/package.json index 4288db48..cef6b487 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,26 @@ "title": "Refresh", "category": "Azure Functions", "icon": { - "light": "resources/light/refresh.svg", - "dark": "resources/dark/refresh.svg" + "light": "resources/light/Refresh.svg", + "dark": "resources/dark/Refresh.svg" + } + }, + { + "command": "azureFunctions.initFunctionApp", + "title": "Initialize Function App", + "category": "Azure Functions", + "icon": { + "light": "resources/light/InitFunctionApp.svg", + "dark": "resources/dark/InitFunctionApp.svg" + } + }, + { + "command": "azureFunctions.createFunction", + "title": "Create Function", + "category": "Azure Functions", + "icon": { + "light": "resources/light/AddFunction.svg", + "dark": "resources/dark/AddFunction.svg" } }, { @@ -56,9 +74,19 @@ "menus": { "view/title": [ { - "command": "azureFunctions.refresh", + "command": "azureFunctions.initFunctionApp", + "when": "view == azureFunctionsExplorer", + "group": "navigation@1" + }, + { + "command": "azureFunctions.createFunction", "when": "view == azureFunctionsExplorer", "group": "navigation@2" + }, + { + "command": "azureFunctions.refresh", + "when": "view == azureFunctionsExplorer", + "group": "navigation@3" } ], "view/item/context": [ diff --git a/resources/dark/AddFunction.svg b/resources/dark/AddFunction.svg new file mode 100644 index 00000000..68af1b45 --- /dev/null +++ b/resources/dark/AddFunction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/dark/InitFunctionApp.svg b/resources/dark/InitFunctionApp.svg new file mode 100644 index 00000000..3446cddc --- /dev/null +++ b/resources/dark/InitFunctionApp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/light/AddFunction.svg b/resources/light/AddFunction.svg new file mode 100644 index 00000000..4370c126 --- /dev/null +++ b/resources/light/AddFunction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/light/InitFunctionApp.svg b/resources/light/InitFunctionApp.svg new file mode 100644 index 00000000..d7214dd0 --- /dev/null +++ b/resources/light/InitFunctionApp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/commands.ts b/src/commands.ts index 00696e5e..f0c9666c 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -5,12 +5,32 @@ import * as vscode from 'vscode'; import * as opn from 'opn'; +import * as util from './util'; import { INode } from './nodes'; +import { FunctionsCli } from './functions-cli'; +import { QuickPickItemWithData } from './util'; -export class AzureFunctionsCommands { - public static openInPortal(node?: INode) { - if (node && node.tenantId) { - opn(`https://portal.azure.com/${node.tenantId}/#resource${node.id}`); - } +export function openInPortal(node?: INode) { + if (node && node.tenantId) { + opn(`https://portal.azure.com/${node.tenantId}/#resource${node.id}`); } +} + +export async function createFunction(functionsCli: FunctionsCli) { + const templates = [ + new QuickPickItemWithData("BlobTrigger"), + new QuickPickItemWithData("EventGridTrigger"), + new QuickPickItemWithData("HttpTrigger"), + new QuickPickItemWithData("QueueTrigger"), + new QuickPickItemWithData("TimerTrigger") + ]; + const template = await util.showQuickPick(templates, "Select a function template"); + + const name = await util.showInputBox("Function Name", "Provide a function name"); + + functionsCli.createFunction(template.label, name); +} + +export async function initFunctionApp(functionsCli: FunctionsCli) { + functionsCli.initFunctionApp("TODO"); } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 0abf14f4..16922e50 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,12 +7,13 @@ import * as vscode from 'vscode'; import * as util from "./util"; +import * as commands from './commands'; import { AzureAccount } from './azure-account.api'; import { AzureFunctionsExplorer } from './explorer'; import { INode } from './nodes' import { Reporter } from './telemetry'; -import { AzureFunctionsCommands } from './commands'; +import { FunctionsCli } from './functions-cli' export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(new Reporter(context)); @@ -25,8 +26,14 @@ export function activate(context: vscode.ExtensionContext) { const explorer = new AzureFunctionsExplorer(azureAccount); context.subscriptions.push(vscode.window.registerTreeDataProvider('azureFunctionsExplorer', explorer)); + const terminal: vscode.Terminal = vscode.window.createTerminal('Azure Functions'); + context.subscriptions.push(terminal); + const functionsCli = new FunctionsCli(terminal); + initCommand(context, 'azureFunctions.refresh', (node?: INode) => explorer.refresh(node)); - initCommand(context, 'azureFunctions.openInPortal', (node?: INode) => AzureFunctionsCommands.openInPortal(node)); + initCommand(context, 'azureFunctions.openInPortal', (node?: INode) => commands.openInPortal(node)); + initCommand(context, 'azureFunctions.createFunction', (node?: INode) => commands.createFunction(functionsCli)); + initCommand(context, 'azureFunctions.initFunctionApp', (node?: INode) => commands.initFunctionApp(functionsCli)); } else { vscode.window.showErrorMessage("The Azure Account Extension is required for the Azure Functions extension."); } @@ -48,9 +55,14 @@ function initAsyncCommand(context: vscode.ExtensionContext, commandId: string, c try { await callback(...args); } catch (error) { - result = 'Failed'; - errorData = util.errorToString(error); - throw error; + if (error instanceof util.UserCancelledError) { + result = 'Canceled'; + // Swallow the error so that it's not displayed to the user + } else { + result = 'Failed'; + errorData = util.errorToString(error); + throw error; + } } finally { const end = Date.now(); const properties: { [key: string]: string; } = { result: result }; diff --git a/src/functions-cli.ts b/src/functions-cli.ts new file mode 100644 index 00000000..534b40d0 --- /dev/null +++ b/src/functions-cli.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as util from './util'; + +export class FunctionsCli { + constructor(private readonly _terminal: vscode.Terminal) { + } + + createFunction(templateName: string, name: string) { + this.executeCommand(`func new --language JavaScript --template ${templateName} --name ${name}`); + } + + async initFunctionApp(name: string) { + this.executeCommand(`func init`); + } + + private executeCommand(command: string) { + // TODO: Verify terminal is in current working folder + this._terminal.show(); + this._terminal.sendText(command); + } +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index dbb51481..b97ebccd 100644 --- a/src/util.ts +++ b/src/util.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { reporter } from './telemetry'; +import * as vscode from 'vscode'; export function sendTelemetry(eventName: string, properties?: { [key: string]: string; }, measures?: { [key: string]: number; }) { if (reporter) { @@ -28,4 +29,43 @@ export function errorToString(error: any): string | undefined { return error.toString(); } -} \ No newline at end of file +} + +export async function showQuickPick(items: QuickPickItemWithData[] | Thenable[]>, placeHolder: string, token?: vscode.CancellationToken): Promise> { + const options: vscode.QuickPickOptions = { + placeHolder: placeHolder, + ignoreFocusOut: true + } + const result = await vscode.window.showQuickPick(items, options, token); + + if (!result) { + throw new UserCancelledError(); + } + + return result; +} + +export async function showInputBox(placeHolder: string, prompt: string): Promise { + const options: vscode.InputBoxOptions = { + placeHolder: placeHolder, + prompt: prompt, + // TODO: validateInput + ignoreFocusOut: true + } + const result = await vscode.window.showInputBox(options); + + if (!result) { + throw new UserCancelledError(); + } + + return result; +} + +export class QuickPickItemWithData implements vscode.QuickPickItem { + readonly description: string; + constructor(readonly label: string, description?: string, readonly data?: T) { + this.description = description ? description : ''; + } +} + +export class UserCancelledError extends Error { } \ No newline at end of file