From bd1f8d88b443f582ad020a3347d5feacf512751c Mon Sep 17 00:00:00 2001 From: Nikhil Yogendra Murali <25237515+nikhilym@users.noreply.github.com> Date: Wed, 2 Sep 2020 15:04:08 -0700 Subject: [PATCH] feat: Add telemetry for hosted skills clone, open URLs, and SMAPI user agent (#34) This commit introduces the following changes : - A new cloneSkillFromConsole command which handles cloning the hosted skills from developer console. - Reformatting common logic from cloneSkill and cloneSkillFromConsole command to a new utility function. - Setting the user agent specific to extension for SMAPI SDK initialization. - Setting correct telemetry command names for open URL commands. --- src/askContainer/commands/cloneSkill.ts | 78 +------------------ .../commands/cloneSkillFromConsole.ts | 18 +++++ .../treeViewProviders/helpViewProvider.ts | 10 +-- .../skillActionsViewProvider.ts | 4 +- src/extension.ts | 3 +- src/runtime/lib/API.ts | 5 +- src/runtime/lib/smapiClientFactory.ts | 7 +- src/utils/cloneSkillHelper.ts | 74 ++++++++++++++++++ src/utils/urlHandlers.ts | 2 +- 9 files changed, 113 insertions(+), 88 deletions(-) create mode 100644 src/askContainer/commands/cloneSkillFromConsole.ts diff --git a/src/askContainer/commands/cloneSkill.ts b/src/askContainer/commands/cloneSkill.ts index 490ef83..a779305 100644 --- a/src/askContainer/commands/cloneSkill.ts +++ b/src/askContainer/commands/cloneSkill.ts @@ -1,17 +1,10 @@ -import * as vscode from 'vscode'; -import * as fs from 'fs'; -import * as fsExtra from 'fs-extra'; -import * as path from 'path'; import { - SmapiResource, AbstractCommand, CommandContext, Utils + SmapiResource, AbstractCommand, CommandContext } from '../../runtime'; import { SkillInfo } from '../../models/types'; -import { getSkillNameFromLocales } from '../../utils/skillHelper'; -import { cloneSkill } from '../../utils/cloneSkillHelper'; -import { openWorkspaceFolder } from '../../utils/workspaceHelper'; +import { executeClone } from '../../utils/cloneSkillHelper'; import { Logger } from '../../logger'; -import { loggableAskError } from '../../exceptions'; export class CloneSkillCommand extends AbstractCommand { @@ -19,73 +12,8 @@ export class CloneSkillCommand extends AbstractCommand { super('askContainer.skillsConsole.cloneSkill'); } - async createSkillFolder(skillInfo: SmapiResource): Promise { - Logger.verbose(`Calling method: ${this.commandName}.createSkillFolder, args: `, skillInfo); - const selectedFolderArray = await vscode.window.showOpenDialog( - { - openLabel: 'Select project folder', - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false - } - ); - if (selectedFolderArray === undefined) { - return undefined; - } - - const projectFolder = selectedFolderArray[0]; - const skillName = getSkillNameFromLocales( - skillInfo.data.skillSummary.nameByLocale!); - const filteredProjectName = Utils.filterNonAlphanumeric(skillName) - const skillFolderAbsPath = path.join(projectFolder.fsPath, filteredProjectName); - - // create skill folder in project path - if (fs.existsSync(skillFolderAbsPath)) { - Logger.debug(`Skill folder ${skillFolderAbsPath} already exists.`); - const errorMessage = `Skill folder ${skillFolderAbsPath} already exists. Would you like to overwrite it?`; - const overWriteSelection = await vscode.window.showInformationMessage(errorMessage, ...['Yes', 'No']); - if (overWriteSelection === 'Yes') { - Logger.debug(`Confirmed skill folder overwrite option. Overwriting ${skillFolderAbsPath}.`); - fsExtra.removeSync(skillFolderAbsPath); - } - else { - return undefined; - } - } - - fs.mkdirSync(skillFolderAbsPath); - - return vscode.Uri.file(skillFolderAbsPath); - } - async execute(context: CommandContext, skillInfo: SmapiResource): Promise { Logger.debug(`Calling method: ${this.commandName}, args: `, skillInfo); - try { - const skillFolderUri = await this.createSkillFolder(skillInfo); - if (skillFolderUri === undefined) { - return; - } - // Create progress bar and run next steps - await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: "Downloading skill", - cancellable: false - }, async (progress, token) => { - await cloneSkill( - skillInfo, skillFolderUri.fsPath, - context.extensionContext, progress); - }); - const skillName = getSkillNameFromLocales(skillInfo.data.skillSummary.nameByLocale!); - const cloneSkillMsg = `Skill ${skillName} was cloned successfully and added to workspace. The skill is located at ${skillFolderUri.fsPath}`; - - Logger.info(cloneSkillMsg); - vscode.window.showInformationMessage(cloneSkillMsg); - - // Add skill folder to workspace - await openWorkspaceFolder(skillFolderUri); - return; - } catch (err) { - throw loggableAskError(`Skill clone failed`, err, true); - } + await executeClone(context, skillInfo); } } diff --git a/src/askContainer/commands/cloneSkillFromConsole.ts b/src/askContainer/commands/cloneSkillFromConsole.ts new file mode 100644 index 0000000..1878e10 --- /dev/null +++ b/src/askContainer/commands/cloneSkillFromConsole.ts @@ -0,0 +1,18 @@ +import { + SmapiResource, AbstractCommand, CommandContext +} from '../../runtime'; + +import { SkillInfo } from '../../models/types'; +import { executeClone } from '../../utils/cloneSkillHelper'; +import { Logger } from '../../logger'; + +export class CloneSkillFromConsoleCommand extends AbstractCommand { + constructor() { + super('askContainer.skillsConsole.cloneSkillFromConsole'); + } + + async execute(context: CommandContext, skillInfo: SmapiResource): Promise { + Logger.debug(`Calling method: ${this.commandName}, args: `, skillInfo); + await executeClone(context, skillInfo); + } +} \ No newline at end of file diff --git a/src/askContainer/treeViews/treeViewProviders/helpViewProvider.ts b/src/askContainer/treeViews/treeViewProviders/helpViewProvider.ts index 2a29715..a36f30c 100644 --- a/src/askContainer/treeViews/treeViewProviders/helpViewProvider.ts +++ b/src/askContainer/treeViews/treeViewProviders/helpViewProvider.ts @@ -53,7 +53,7 @@ export class HelpViewProvider implements vscode.TreeDataProvider { diff --git a/src/runtime/lib/API.ts b/src/runtime/lib/API.ts index 8c000c8..140c0a3 100644 --- a/src/runtime/lib/API.ts +++ b/src/runtime/lib/API.ts @@ -25,7 +25,7 @@ export function registerCommands(context: ExtensionContext, commands: GenericCom } export abstract class AbstractCommand implements GenericCommand, Command { - // Need this for adding AbstractCommand as a valid type t + // Need this for adding AbstractCommand as a valid type title: string; command: string; tooltip?: string; @@ -51,7 +51,8 @@ export abstract class AbstractCommand implements GenericCommand, Command { } private async _invoke(commandName: string, ...args: any[]): Promise { - const commandType = 'command'; + const typeArg = args.find(arg => arg.CommandType); + const commandType = typeArg ? typeArg.CommandType : 'command'; const telemetryClient = new TelemetryClient({}); let output: any; diff --git a/src/runtime/lib/smapiClientFactory.ts b/src/runtime/lib/smapiClientFactory.ts index 36a84e1..caf96a9 100644 --- a/src/runtime/lib/smapiClientFactory.ts +++ b/src/runtime/lib/smapiClientFactory.ts @@ -1,16 +1,17 @@ +import * as vscode from 'vscode'; import { RefreshTokenConfig, CustomSmapiClientBuilder } from "ask-smapi-sdk"; import * as smapiModel from 'ask-smapi-model'; import { CredentialsManager, Credentials } from "./credentialsManager"; import { resolver } from "./utils/configuration"; -import { ExtensionContext } from "vscode"; import { AUTH } from "./utils/constants"; +import { EXTENSION_ID } from '../../constants'; export class SmapiClientFactory { private static readonly profileInstanceMap: Map = new Map(); private constructor() { } - public static getInstance(profile: string, context: ExtensionContext): smapiModel.services.skillManagement.SkillManagementServiceClient { + public static getInstance(profile: string, context: vscode.ExtensionContext): smapiModel.services.skillManagement.SkillManagementServiceClient { let smapiClient = this.profileInstanceMap.get(profile); if (!smapiClient) { const authConfig: Credentials = CredentialsManager.getCredentials(profile); @@ -19,10 +20,12 @@ export class SmapiClientFactory { clientSecret: authConfig.clientSecret, refreshToken: authConfig.refreshToken }; + const pluginVersion: string = vscode.extensions.getExtension(EXTENSION_ID)?.packageJSON.version; smapiClient = new CustomSmapiClientBuilder() .withApiEndpoint(resolver([process.env.ASK_SMAPI_SERVER_BASE_URL, undefined])) .withAuthEndpoint(resolver([process.env.ASK_LWA_TOKEN_HOST, AUTH.DEFAULT_ASK_LWA_TOKEN_HOST])) .withRefreshTokenConfig(refreshTokenConfig) + .withCustomUserAgent(`alexa-skills-kit-toolkit/${pluginVersion}`) .client(); this.profileInstanceMap.set(profile, smapiClient); } diff --git a/src/utils/cloneSkillHelper.ts b/src/utils/cloneSkillHelper.ts index 95e0977..e679f55 100644 --- a/src/utils/cloneSkillHelper.ts +++ b/src/utils/cloneSkillHelper.ts @@ -5,8 +5,10 @@ import { } from '../runtime'; import * as R from 'ramda'; import * as fs from 'fs'; +import * as fsExtra from 'fs-extra'; import * as https from 'https'; +import { CommandContext } from '../runtime'; import { SkillInfo } from '../models/types'; import { GitInTerminalHelper, getOrInstantiateGitApi, isGitInstalled } from './gitHelper'; import { createSkillPackageFolder, syncSkillPackage } from './skillPackageHelper'; @@ -15,6 +17,77 @@ import { SKILL_FOLDER, BASE_RESOURCES_CONFIG, DEFAULT_PROFILE, BASE_STATES_CONFIG, SKILL, GIT_MESSAGES, CLI_HOSTED_SKILL_TYPE } from '../constants'; import { Logger } from '../logger'; import { loggableAskError, AskError } from '../exceptions'; +import { getSkillNameFromLocales } from '../utils/skillHelper'; +import { openWorkspaceFolder } from '../utils/workspaceHelper'; + +export async function executeClone(context: CommandContext, skillInfo: SmapiResource) { + try { + const skillFolderUri = await createSkillFolder(skillInfo); + if (skillFolderUri === undefined) { + return; + } + // Create progress bar and run next steps + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Downloading skill", + cancellable: false + }, async (progress, token) => { + await cloneSkill( + skillInfo, skillFolderUri.fsPath, + context.extensionContext, progress); + }); + const skillName = getSkillNameFromLocales(skillInfo.data.skillSummary.nameByLocale!); + const cloneSkillMsg = `Skill ${skillName} was cloned successfully and added to workspace. The skill is located at ${skillFolderUri.fsPath}`; + + Logger.info(cloneSkillMsg); + vscode.window.showInformationMessage(cloneSkillMsg); + + // Add skill folder to workspace + await openWorkspaceFolder(skillFolderUri); + return; + } catch (err) { + throw loggableAskError(`Skill clone failed`, err, true); + } +} + +async function createSkillFolder(skillInfo: SmapiResource): Promise { + Logger.verbose(`Calling method: createSkillFolder, args: `, skillInfo); + const selectedFolderArray = await vscode.window.showOpenDialog( + { + openLabel: 'Select project folder', + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false + } + ); + if (selectedFolderArray === undefined) { + return undefined; + } + + const projectFolder = selectedFolderArray[0]; + const skillName = getSkillNameFromLocales( + skillInfo.data.skillSummary.nameByLocale!); + const filteredProjectName = Utils.filterNonAlphanumeric(skillName); + const skillFolderAbsPath = path.join(projectFolder.fsPath, filteredProjectName); + + // create skill folder in project path + if (fs.existsSync(skillFolderAbsPath)) { + Logger.debug(`Skill folder ${skillFolderAbsPath} already exists.`); + const errorMessage = `Skill folder ${skillFolderAbsPath} already exists. Would you like to overwrite it?`; + const overWriteSelection = await vscode.window.showInformationMessage(errorMessage, ...['Yes', 'No']); + if (overWriteSelection === 'Yes') { + Logger.debug(`Confirmed skill folder overwrite option. Overwriting ${skillFolderAbsPath}.`); + fsExtra.removeSync(skillFolderAbsPath); + } + else { + return undefined; + } + } + + fs.mkdirSync(skillFolderAbsPath); + + return vscode.Uri.file(skillFolderAbsPath); +} async function setupGitFolder( skillInfo: SmapiResource, targetPath: string, context: vscode.ExtensionContext): Promise { @@ -191,3 +264,4 @@ function filesToIgnore(): string[] { const nodeModules = `${SKILL_FOLDER.LAMBDA.NAME}/${SKILL_FOLDER.LAMBDA.NODE_MODULES}`; return [SKILL_FOLDER.ASK_RESOURCES_JSON_CONFIG, SKILL_FOLDER.HIDDEN_ASK_FOLDER, SKILL_FOLDER.HIDDEN_VSCODE ,nodeModules]; } + diff --git a/src/utils/urlHandlers.ts b/src/utils/urlHandlers.ts index b2c4831..022fd45 100644 --- a/src/utils/urlHandlers.ts +++ b/src/utils/urlHandlers.ts @@ -45,7 +45,7 @@ export async function hostedSkillsClone(uri: vscode.Uri, context: vscode.Extensi const targetSkill: SkillSummary = listSkills.skills![0]; if (targetSkill) { vscode.commands.executeCommand( - 'askContainer.skillsConsole.cloneSkill', new SmapiResource( + 'askContainer.skillsConsole.cloneSkillFromConsole', new SmapiResource( new SkillInfo(targetSkill, true, hostedSkillMetadata), targetSkillId)); return; }