Skip to content

Commit

Permalink
feat: add "ask api get-metrics" and "ask api export-package" commands
Browse files Browse the repository at this point in the history
  • Loading branch information
RonWang committed Jan 28, 2020
1 parent 0c1ea02 commit eb94433
Show file tree
Hide file tree
Showing 13 changed files with 1,042 additions and 6 deletions.
65 changes: 64 additions & 1 deletion lib/clients/smapi-client/resources/skill.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,68 @@ module.exports = (smapiHandle) => {
);
}

/**
* Get calculated metrics, insights and advanced analytics report for skills usage.
* @param {String} skillId [required] Unique identifier of skill.
* @param {String} startTime [required] The start time of query.
* @param {String} endTime [required] The end time of the query. The maximum duration is one week.
* @param {Enum} period [required] The aggregation period that is used when retrieving the metric. The values are SINGLE, PT15M, PT1H, P1D.
* @param {Enum} metric [required] A distinct set of logic that predictably returns a set of data.
* @param {Enum} stage [required] This parameter shows the stage of the skill. The accepted values are: live or development.
* @param {Enum} skillType [required] The type of skill. Potential values are: custom, smartHome, or flashBriefing.
* @param {String} intent The skill intent.
* @param {String} locale The locale of the skill.
* @param {String} maxResults The maximum number of results to display per page (100,000 is the maximum number of results).
* @param {String} nextToken The continuation token returned in response to an object of the last get metrics report response.
* @param {callback} callback callback function
*/
function getMetrics(skillId, startTime, endTime, period, metric, stage, skillType,
intent, locale, maxResults, nextToken, callback) {
const queryObject = {};
if (startTime) {
queryObject.startTime = startTime;
}
if (endTime) {
queryObject.endTime = endTime;
}
if (period) {
queryObject.period = period;
}
if (metric) {
queryObject.metric = metric;
}
if (stage) {
queryObject.stage = stage;
}
if (skillType) {
queryObject.skillType = skillType;
}
if (intent) {
queryObject.intent = intent;
}
if (locale) {
queryObject.locale = locale;
}

queryObject.maxResults = maxResults || CONSTANTS.SMAPI.DEFAULT_MAX_RESULT_PER_PAGE;

if (nextToken) {
queryObject.nextToken = nextToken;
}

const url = `skills/${skillId}/metrics`;
smapiHandle(
CONSTANTS.SMAPI.API_NAME.GET_METRICS,
CONSTANTS.HTTP_REQUEST.VERB.GET,
CONSTANTS.SMAPI.VERSION.V1,
url,
queryObject,
EMPTY_HEADERS,
NULL_PAYLOAD,
callback
);
}

return {
createSkill,
deleteSkill,
Expand All @@ -211,6 +273,7 @@ module.exports = (smapiHandle) => {
getSkillCredentials,
validateSkill,
getValidation,
listIspForSkill
listIspForSkill,
getMetrics
};
};
6 changes: 6 additions & 0 deletions lib/commands/api/api-commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const API_COMMAND_MAP = {
'delete-isp': '@src/commands/api/isp/delete-isp',
'reset-isp-entitlement': '@src/commands/api/isp/reset-isp-entitlement',

// metrics
'get-metrics': '@src/commands/api/metrics/get-metrics',

// skill
'create-skill': '@src/commands/api/skill/create-skill',
'delete-skill': '@src/commands/api/skill/delete-skill',
Expand Down Expand Up @@ -85,6 +88,9 @@ const API_COMMAND_MAP = {
'get-certification': '@src/commands/api/publishing/get-certification',
'list-certifications': '@src/commands/api/publishing/list-certifications',

// skill:package
'export-package': '@src/commands/api/skill-package/export-package',

// skill:test
'simulate-skill': '@src/commands/api/test/simulate-skill',
'get-simulation': '@src/commands/api/test/get-simulation',
Expand Down
58 changes: 58 additions & 0 deletions lib/commands/api/metrics/get-metrics/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { AbstractCommand } = require('@src/commands/abstract-command');
const Messenger = require('@src/view/messenger');
const jsonView = require('@src/view/json-view');
const SmapiClient = require('@src/clients/smapi-client');
const optionModel = require('@src/commands/option-model');
const profileHelper = require('@src/utils/profile-helper');

class GetMetricsCommand extends AbstractCommand {
name() {
return 'get-metrics';
}

description() {
return 'get calculated metrics, insights, and advanced analytics reporting for skills usage.';
}

requiredOptions() {
return ['skill-id', 'start-time', 'end-time', 'period', 'metric', 'stage', 'skill-type'];
}

optionalOptions() {
return ['intent', 'locale', 'max-results', 'next-token', 'profile', 'debug'];
}

handle(cmd, cb) {
let profile;
try {
profile = profileHelper.runtimeProfile(cmd.profile);
} catch (err) {
Messenger.getInstance().error(err);
return cb(err);
}

const smapiClient = new SmapiClient({
profile,
doDebug: cmd.debug
});
smapiClient.skill.getMetrics(cmd.skillId, cmd.startTime, cmd.endTime, cmd.period, cmd.metric,
cmd.stage, cmd.skillType, cmd.intent, cmd.locale, cmd.nextToken, cmd.maxResults, (err, response) => {
if (err) {
Messenger.getInstance().error(err);
return cb(err);
}
if (response.statusCode >= 300) {
const error = jsonView.toString(response.body);
Messenger.getInstance().error(error);
cb(error);
} else {
Messenger.getInstance().info(jsonView.toString(response.body));
cb();
}
});
}
}

module.exports = {
createCommand: new GetMetricsCommand(optionModel).createCommand()
};
34 changes: 34 additions & 0 deletions lib/commands/api/skill-package/export-package/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

const CONSTANTS = require('@src/utils/constants');
const jsonView = require('@src/view/json-view');
const Retry = require('@src/utils/retry-utility');

module.exports = {
pollExportStatus
};

/**
* Wrapper for polling smapi skill package export status.
* @param {String} exportId
* @param {Function} callback (err, lastExportStatus)
*/
function pollExportStatus(smapiClient, exportId, callback) {
const retryConfig = {
base: CONSTANTS.CONFIGURATION.RETRY.GET_PACKAGE_EXPORT_STATUS.MIN_TIME_OUT,
factor: CONSTANTS.CONFIGURATION.RETRY.GET_PACKAGE_EXPORT_STATUS.FACTOR,
maxRetry: CONSTANTS.CONFIGURATION.RETRY.GET_PACKAGE_EXPORT_STATUS.MAX_RETRY
};
const retryCall = (loopCallback) => {
smapiClient.skillPackage.getExportStatus(exportId, (pollErr, pollResponse) => {
if (pollErr) {
return loopCallback(pollErr);
}
if (pollResponse.statusCode >= 300) {
return loopCallback(jsonView.toString(pollResponse.body));
}
loopCallback(null, pollResponse);
});
};
const shouldRetryCondition = retryResponse => retryResponse.body.status === CONSTANTS.SKILL.PACKAGE_STATUS.IN_PROGRESS;
Retry.retry(retryConfig, retryCall, shouldRetryCondition, (err, res) => callback(err, err ? null : res));
}
87 changes: 87 additions & 0 deletions lib/commands/api/skill-package/export-package/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const fs = require('fs');
const path = require('path');
const R = require('ramda');

const { AbstractCommand } = require('@src/commands/abstract-command');
const CONSTANTS = require('@src/utils/constants');
const jsonView = require('@src/view/json-view');
const Messenger = require('@src/view/messenger');
const optionModel = require('@src/commands/option-model');
const profileHelper = require('@src/utils/profile-helper');
const SmapiClient = require('@src/clients/smapi-client/index.js');
const zipUtils = require('@src/utils/zip-utils');

const helper = require('./helper.js');

class ExportPackageCommand extends AbstractCommand {
name() {
return 'export-package';
}

description() {
return 'download the skill package to "skill-package" folder in current directory';
}

requiredOptions() {
return ['skill-id', 'stage'];
}

optionalOptions() {
return ['profile', 'debug'];
}

handle(cmd, cb) {
let profile;
try {
profile = profileHelper.runtimeProfile(cmd.profile);
// 0.check if a skill-package file exists
if (fs.existsSync(CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE)) {
throw new Error(`A ${CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE} fold already exists in the current working directory.`);
}
} catch (err) {
Messenger.getInstance().error(err);
return cb(err);
}
const smapiClient = new SmapiClient({
profile,
doDebug: cmd.debug
});

// 1.request to export skill package
smapiClient.skillPackage.exportPackage(cmd.skillId, cmd.stage, (exportErr, exportResponse) => {
if (exportErr) {
Messenger.getInstance().error(exportErr);
return cb(exportErr);
}
if (exportResponse.statusCode >= 300) {
const error = jsonView.toString(exportResponse.body);
Messenger.getInstance().error(error);
return cb(error);
}
const exportId = path.basename(R.view(R.lensPath(['headers', 'location']), exportResponse));

// 2.poll for the skill package export status
helper.pollExportStatus(smapiClient, exportId, (pollErr, pollResponse) => {
if (pollErr) {
Messenger.getInstance().error(pollErr);
return cb(pollErr);
}
// 3.download skill package into local file system
const skillPackageLocation = R.view(R.lensPath(['body', 'skill', 'location']), pollResponse);
const rootPath = process.cwd();
const targetPath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE);
zipUtils.unzipRemoteZipFile(skillPackageLocation, targetPath, false, (unzipErr) => {
if (unzipErr) {
Messenger.getInstance().error(unzipErr);
return cb(unzipErr);
}
Messenger.getInstance().info(`The skill package had been downloaded into ${targetPath}.`);
cb();
});
});
});
}
}

module.exports = ExportPackageCommand;
module.exports.createCommand = new ExportPackageCommand(optionModel).createCommand();
49 changes: 49 additions & 0 deletions lib/commands/option-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,54 @@
"description": "skill id of task provider",
"alias": null,
"stringInput": "REQUIRED"
},
"start-time": {
"name": "start-time",
"description": "the start time of query",
"alias": null,
"stringInput": "REQUIRED"
},
"end-time": {
"name": "end-time",
"description": "the end time of the query (maximum duration one week)",
"alias": null,
"stringInput": "REQUIRED"
},
"period": {
"name": "period",
"description": "the aggregation period that is used when retrieving the metric",
"alias": null,
"stringInput": "REQUIRED",
"rule": [{
"type": "ENUM",
"values": ["SINGLE", "PT15M", "PT1H", "P1D"]
}]
},
"metric": {
"name": "metric",
"description": "a distinct set of logic that predictably returns a set of data",
"alias": null,
"stringInput": "REQUIRED",
"rule": [{
"type": "ENUM",
"values": ["uniqueCustomers", "totalEnablements", "totalUtterances", "successfulUtterances", "failedUtterances", "totalSessions",
"successfulSessions", "incompleteSessions", "userEndedSessions", "skillEndedSessions"]
}]
},
"skill-type": {
"name": "skill-type",
"description": "the type of skill",
"alias": null,
"stringInput": "REQUIRED",
"rule": [{
"type": "ENUM",
"values": ["custom", "smartHome", "flashBriefing"]
}]
},
"intent": {
"name": "intent",
"description": "the skill intent",
"alias": null,
"stringInput": "REQUIRED"
}
}
1 change: 1 addition & 0 deletions lib/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ module.exports.SMAPI = {
GET_SKILL_ENABLEMENT: 'get-skill-enablement',
GET_SKILL_CREDENTIALS: 'get-skill-credentials',
LIST_VENDORS: 'list-vendors',
GET_METRICS: 'get-metrics',
// Evaluations
NLU_PROFILE: 'nlu-profile',
// Utterance transcripts
Expand Down
Loading

0 comments on commit eb94433

Please sign in to comment.