Skip to content

Commit

Permalink
feat: Add telemetry for hosted skills clone, open URLs, and SMAPI use…
Browse files Browse the repository at this point in the history
…r 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.
  • Loading branch information
nikhilym authored Sep 2, 2020
1 parent 516a207 commit bd1f8d8
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 88 deletions.
78 changes: 3 additions & 75 deletions src/askContainer/commands/cloneSkill.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,19 @@
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<void> {

constructor() {
super('askContainer.skillsConsole.cloneSkill');
}

async createSkillFolder(skillInfo: SmapiResource<SkillInfo>): Promise<vscode.Uri | undefined> {
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<SkillInfo>): Promise<void> {
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);
}
}
18 changes: 18 additions & 0 deletions src/askContainer/commands/cloneSkillFromConsole.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
constructor() {
super('askContainer.skillsConsole.cloneSkillFromConsole');
}

async execute(context: CommandContext, skillInfo: SmapiResource<SkillInfo>): Promise<void> {
Logger.debug(`Calling method: ${this.commandName}, args: `, skillInfo);
await executeClone(context, skillInfo);
}
}
10 changes: 5 additions & 5 deletions src/askContainer/treeViews/treeViewProviders/helpViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class HelpViewProvider implements vscode.TreeDataProvider<PluginTreeItem<
{
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [EXTERNAL_LINKS.RELEASE_UPDATES, true],
arguments: [EXTERNAL_LINKS.RELEASE_UPDATES, true, {CommandType: 'RELEASE_UPDATES'}],
}, undefined, ContextValueTypes.SKILL,
));

Expand All @@ -75,7 +75,7 @@ export class HelpViewProvider implements vscode.TreeDataProvider<PluginTreeItem<
vscode.TreeItemCollapsibleState.None, {
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.CLI, true],
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.CLI, true, {CommandType: 'TOOLS_DOCS_CLI'}],
}, undefined,
ContextValueTypes.SKILL,
));
Expand All @@ -85,7 +85,7 @@ export class HelpViewProvider implements vscode.TreeDataProvider<PluginTreeItem<
vscode.TreeItemCollapsibleState.None, {
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.VSCODE, true],
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.VSCODE, true, {CommandType: 'TOOLS_DOCS_VSCODE'}],
}, undefined,
ContextValueTypes.SKILL,
));
Expand All @@ -103,7 +103,7 @@ export class HelpViewProvider implements vscode.TreeDataProvider<PluginTreeItem<
{
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.ASK_SDK, true],
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.ASK_SDK, true, {CommandType: 'TOOLS_DOCS_ASK_SDK'}],
}, undefined,
ContextValueTypes.SKILL,
));
Expand All @@ -113,7 +113,7 @@ export class HelpViewProvider implements vscode.TreeDataProvider<PluginTreeItem<
vscode.TreeItemCollapsibleState.None, {
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.SMAPI_SDK, true],
arguments: [EXTERNAL_LINKS.TOOLS_DOCS.SMAPI_SDK, true, {CommandType: 'TOOLS_DOCS_SMAPI_SDK'}],
}, undefined,
ContextValueTypes.SKILL,
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class SkillActionsViewProvider implements vscode.TreeDataProvider<PluginT
{
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [getSimulatorLink(skillId, skillDetails.defaultLocale)],
arguments: [getSimulatorLink(skillId, skillDetails.defaultLocale), {CommandType: 'SIMULATOR'}],
}, undefined,
ContextValueTypes.SKILL,
),
Expand Down Expand Up @@ -294,7 +294,7 @@ export class SkillActionsViewProvider implements vscode.TreeDataProvider<PluginT
{
title: 'openUrl',
command: 'ask.container.openUrl',
arguments: [getIModelGeneratorLink(skillId, skillDetails.defaultLocale)],
arguments: [getIModelGeneratorLink(skillId, skillDetails.defaultLocale), {CommandType: 'IM_EDITOR'}],
}, undefined,
ContextValueTypes.SKILL,
),
Expand Down
3 changes: 2 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as vscode from 'vscode';
import { registerCommands as apiRegisterCommands, AbstractWebView, Utils } from './runtime';

import { CloneSkillCommand } from './askContainer/commands/cloneSkill';
import { CloneSkillFromConsoleCommand } from './askContainer/commands/cloneSkillFromConsole';
import { DeploySkillCommand } from './askContainer/commands/deploySkill';
import { SyncInteractionModelCommand } from './askContainer/commands/syncInteractionModel';
import { SyncManifestCommand } from './askContainer/commands/syncManifest';
Expand Down Expand Up @@ -59,7 +60,7 @@ function registerCommands(context: vscode.ExtensionContext): void {
new InitCommand(profileManager), new GetToolkitInfoCommand(),
new ViewAllSkillsCommand(), new CreateSkillCommand(createSkill),
new CloneSkillCommand(), new ChangeProfileCommand(), new AccessTokenCommand(),
new DebugAdapterPathCommand()]);
new DebugAdapterPathCommand(), new CloneSkillFromConsoleCommand()]);
}

async function registerSkillActionComponents(context: vscode.ExtensionContext): Promise<void> {
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/lib/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function registerCommands(context: ExtensionContext, commands: GenericCom
}

export abstract class AbstractCommand<T> 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;
Expand All @@ -51,7 +51,8 @@ export abstract class AbstractCommand<T> implements GenericCommand, Command {
}

private async _invoke(commandName: string, ...args: any[]): Promise<T> {
const commandType = 'command';
const typeArg = args.find(arg => arg.CommandType);
const commandType = typeArg ? typeArg.CommandType : 'command';
const telemetryClient = new TelemetryClient({});
let output: any;

Expand Down
7 changes: 5 additions & 2 deletions src/runtime/lib/smapiClientFactory.ts
Original file line number Diff line number Diff line change
@@ -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<String, smapiModel.services.skillManagement.SkillManagementServiceClient> = 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);
Expand All @@ -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);
}
Expand Down
74 changes: 74 additions & 0 deletions src/utils/cloneSkillHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<SkillInfo>) {
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<SkillInfo>): Promise<vscode.Uri | undefined> {
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<SkillInfo>, targetPath: string, context: vscode.ExtensionContext): Promise<void> {
Expand Down Expand Up @@ -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];
}

2 changes: 1 addition & 1 deletion src/utils/urlHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkillInfo>(
'askContainer.skillsConsole.cloneSkillFromConsole', new SmapiResource<SkillInfo>(
new SkillInfo(targetSkill, true, hostedSkillMetadata), targetSkillId));
return;
}
Expand Down

0 comments on commit bd1f8d8

Please sign in to comment.