Skip to content

Commit

Permalink
feat: link to our API docs when you --help (#283)
Browse files Browse the repository at this point in the history
* Added help doc url for APIs and commands

* Review comments changes

* Adding some more test cases and minor tweaks
  • Loading branch information
shrutiburman committed Sep 2, 2021
1 parent 0711e97 commit 37a857d
Show file tree
Hide file tree
Showing 10 changed files with 457 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/base-commands/twilio-api-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { doesObjectHaveProperty } = require('@twilio/cli-core').services.JSUtils;
const { logger } = require('@twilio/cli-core').services.logging;
const { camelCase } = require('@twilio/cli-core').services.namingConventions;

const { ApiCommandRunner, getActionDescription, getFlagConfig } = require('../services/twilio-api');
const { ApiCommandRunner, getActionDescription, getFlagConfig, getDocLink } = require('../services/twilio-api');

// Open API type to oclif flag type mapping. For numerical types, we'll do validation elsewhere.
const typeMap = {
Expand Down Expand Up @@ -124,6 +124,7 @@ TwilioApiCommand.setUpNewCommandClass = (NewCommandClass) => {
NewCommandClass.args = [];
NewCommandClass.flags = Object.assign(cmdFlags, TwilioApiCommand.flags);
NewCommandClass.description = getActionDescription(NewCommandClass.actionDefinition);
NewCommandClass.docLink = getDocLink(NewCommandClass.id);
NewCommandClass.load = () => NewCommandClass;

return NewCommandClass;
Expand Down
10 changes: 9 additions & 1 deletion src/hooks/init/twilio-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ const { logger } = require('@twilio/cli-core').services.logging;
const { TwilioApiBrowser } = require('@twilio/cli-core').services.TwilioApi;

const TwilioApiCommand = require('../../base-commands/twilio-api-command');
const { getTopicName, TOPIC_SEPARATOR, BASE_TOPIC_NAME, CORE_TOPIC_NAME } = require('../../services/twilio-api');
const {
getTopicName,
TOPIC_SEPARATOR,
BASE_TOPIC_NAME,
CORE_TOPIC_NAME,
getDocLink,
} = require('../../services/twilio-api');

const METHOD_TO_ACTION_MAP = {
list: {
Expand Down Expand Up @@ -113,8 +119,10 @@ class TwilioRestApiPlugin extends Plugin {
* the constructor, so we make it a static property
* of the newly created command class.
*/
const commandId = `${actionDefinition.topicName}:${actionDefinition.commandName}`;
const NewCommandClass = class extends TwilioApiCommand {};
NewCommandClass.actionDefinition = actionDefinition;
NewCommandClass.docLink = getDocLink(commandId);
TwilioApiCommand.setUpNewCommandClass(NewCommandClass);
return NewCommandClass;
});
Expand Down
120 changes: 120 additions & 0 deletions src/services/api-doc-mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"path": {
"topics":{
"api":{
"accounts": "usage/api/account",
"core": "usage/api",
"bulkexports": "usage/bulkexport",
"autopilot": "autopilot/api",
"chat": "chat/api",
"conversations": "conversations/api",
"events": "events",
"fax": "fax/api",
"flex": "flex/developer",
"frontline": "frontline/api",
"insights": "voice/insights/api",
"lookups": "lookup/api",
"messaging": "sms",
"monitor": "usage/monitor-alert",
"notify": "notify/api",
"numbers": "phone-numbers/api",
"pricing": "voice/pricing",
"proxy": "proxy/api",
"serverless": "runtime",
"studio": "studio",
"supersim": "iot/supersim/api",
"sync": "sync/api",
"taskrouter": "taskrouter/api",
"trunking": "sip-trunking/api",
"trusthub": "trust-hub/trusthub-rest-api",
"verify": "verify/api",
"video": "video",
"voice": "voice",
"wireless": "iot/wireless/api"
},
"debugger": "flex/end-user-guide/debugger",
"email":"/email",
"feedback": "twilio-cli/quickstart#send-us-feedback",
"phone-numbers": "twilio-cli/quickstart#list-your-phone-numbers",
"plugins": "twilio-cli/plugins",
"profiles": "twilio-cli/general-usage#profiles"
},
"commands":{
"autocomplete" : "twilio-cli/quickstart#install-cli-autocomplete-bash-or-zsh-only",
"login": "twilio-cli/quickstart#login-to-your-twilio-account",
"plugins": "twilio-cli/quickstart#explore-plugins"
}
},
"subpath":{
"chat/v2":{
"credentials": "/credential-resource",
"services": "/service-resource",
"services-channels" : "/channel-resource",
"services-bindings": "/binding-resource" ,
"services-roles": "/role-resource",
"services-users": "user-resource"
},
"conversations/v1":{
"configuration": "/configuration-resource",
"conversation": "/conversation-resource",
"credentials": "/credential-resource",
"participant": "/participant-conversation-resource",
"roles" : "/role-resource",
"services": "/service-resource",
"users": "/user-resource"
},
"events/v1": {
"schemes": "/event-streams/schema-resource",
"sinks": "/event-streams/sink-resource",
"subscriptions": "/event-streams/subscription",
"types": "/event-streams/event-type-resource"
},
"fax/v1": {
"faxes": "/fax-resource"
},
"frontline/v1": {
"users": "/user-resource"
},
"notify/v1": {
"credentials": "/credential-resource",
"services": "/service-resource"
},
"proxy/v1": {
"services": "/service"
},
"serverless/v1": {
"services": "/functions-assets-api/api/service"
},
"studio/v2": {
"flows": "/rest-api/v2/flow"
},
"supersim/v1": {
"commands": "/command-resource",
"fleets": "/fleet-resource",
"network-access-profiles": "/networkaccessprofile-resource",
"networks": "/network-resource",
"sims": "/sim-resource",
"sms-commands": "/smscommand-resource",
"usage-records": "/usage-record-resource"
},
"sync/v1": {
"services": "/service"
},
"taskrouter/v1": {
"workspaces": "/workspace"
},
"trunking/v1": {
"trunks":"/trunk-resource"
},
"verify/v2": {
"services": "/service"
},
"voice/v1": {
"byoc-trunks": "/bring-your-own-carrier-byoc",
"connection-policies": "/bring-your-own-carrier-byoc/api/connectionpolicy-resource",
"dialing-permissions": "/api/dialingpermissions-settings-resource",
"ip-records": "/bring-your-own-carrier-byoc/api/iprecord-resource",
"source-ip-mappings": "/bring-your-own-carrier-byoc/api/sourceipmapping-resource"
}
}
}
65 changes: 65 additions & 0 deletions src/services/twilio-api/get-help-doc-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const jsonMap = require('../api-doc-mapping.json');
// json structure = url: {path : {topics: {}, commands: {}}, subpath : {wherever applicable}}
const urlMap = new Map(Object.entries(jsonMap));

const getSubroute = (cmdDetailsArr, subpathMap) => {
const subdomain = cmdDetailsArr[3];
// apiName/version to locate sub url in json map
const apiVersion = `${cmdDetailsArr[1]}/${cmdDetailsArr[2]}`;
const subUrlArr = subpathMap.get(apiVersion);
if (subUrlArr === undefined) {
return '';
}
const subUrlMap = new Map(Object.entries(subUrlArr));
const subUrl = subUrlMap.get(subdomain);
return subUrl;
};

const getRootPath = (cmdDetailsArr, subpathMap, topicsMap, commandsMap) => {
const topicName = cmdDetailsArr[0];
const apiName = cmdDetailsArr[1];
let url = '';

if (topicName === 'api') {
// get the array of apis and convert to a map
const arrOfAllApis = topicsMap.get(topicName);
if (!arrOfAllApis) {
return '';
}
const mapOfAPIs = new Map(Object.entries(arrOfAllApis));
url = mapOfAPIs.get(apiName);
if (url === undefined) {
return '';
}
url += getSubroute(cmdDetailsArr, subpathMap) || '';
} else {
// its a command or another topic
url = topicsMap.get(topicName) || commandsMap.get(topicName);
}

return url;
};

const getDocLink = (key) => {
// key format : baseTopicName:topicName:actionName
const pathMap = new Map(Object.entries(urlMap.get('path')));
const subpathMap = new Map(Object.entries(urlMap.get('subpath')));
const topicsMap = new Map(Object.entries(pathMap.get('topics')));
const commandsMap = new Map(Object.entries(pathMap.get('commands')));

const baseUrl = 'https://twilio.com/docs/';
let url = '';
const cmdDetailsArr = key.split(':'); // original command array
const cmd = cmdDetailsArr[0];
if (topicsMap.has(cmd) || commandsMap.has(cmd)) {
url = getRootPath(cmdDetailsArr, subpathMap, topicsMap, commandsMap);
} else {
url = 'api/';
}
return baseUrl + (url || '');
};

module.exports = {
getDocLink,
getRootPath,
};
2 changes: 2 additions & 0 deletions src/services/twilio-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { ApiCommandRunner } = require('./api-command-runner');
const { getFlagConfig } = require('./get-flag-config');
const { getTopicName, TOPIC_SEPARATOR, BASE_TOPIC_NAME, CORE_TOPIC_NAME } = require('./get-topic-name');
const { getActionDescription } = require('./get-action-description');
const { getDocLink } = require('./get-help-doc-link');

module.exports = {
ApiCommandRunner,
Expand All @@ -11,4 +12,5 @@ module.exports = {
BASE_TOPIC_NAME,
CORE_TOPIC_NAME,
getActionDescription,
getDocLink,
};
44 changes: 44 additions & 0 deletions src/services/twilio-help/twilio-command-help.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ const CommandHelp = require('@oclif/plugin-help/lib/command.js');
const list = require('@oclif/plugin-help/lib/list');
const chalk = require('chalk');
const indent = require('indent-string');
const util = require('@oclif/plugin-help/lib/util');

const { getDocLink } = require('../twilio-api');
/**
* Extended functionality from @oclif/plugin-help.
* Link: https://github.com/oclif/plugin-help
Expand Down Expand Up @@ -31,6 +33,48 @@ class TwilioCommandHelp extends CommandHelp.default {
return returnList.join('\n');
}

// To add the API help document url
docs() {
const listOfDetails = [];
const helpDoc = this.command.docLink || getDocLink(this.command.id);
if (!helpDoc) {
return '';
}

listOfDetails.push(chalk.bold('MORE INFO'));
listOfDetails.push(indent(helpDoc, 2));

return listOfDetails.join('\n');
}

// overriding to include docs()
generate() {
const cmd = this.command;
const flags = util.sortBy(
Object.entries(cmd.flags || {})
.filter(([, v]) => !v.hidden)
.map(([k, v]) => {
v.name = k;
return v;
}),
(f) => [!f.char, f.char, f.name],
);
const args = (cmd.args || []).filter((a) => !a.hidden);
let output = util
.compact([
this.usage(flags),
this.args(args),
this.flags(flags),
this.description(),
this.aliases(cmd.aliases),
this.examples(cmd.examples || cmd.example),
this.docs(),
])
.join('\n\n');
if (this.opts.stripAnsi) output = stripAnsi(output);
return output;
}

/**
* Forked and refactored from oclif default implementation.
* Link: https://github.com/oclif/plugin-help/blob/master/src/command.ts#L125-L154
Expand Down
25 changes: 25 additions & 0 deletions test/base-commands/twilio-api-command.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,22 @@ describe('base-commands', () => {
},
};

const callApiHelpDoc = {
domainName: 'sync',
commandName: 'services',
path: '',
actionName: 'list',
action: {
description: null,
},
docLink: undefined,
};

const getCommandClass = (actionDefinition = callCreateActionDefinition) => {
const NewCommandClass = class extends TwilioApiCommand {};
NewCommandClass.actionDefinition = actionDefinition;
NewCommandClass.actionDefinition.topicName = `api:${getTopicName(NewCommandClass.actionDefinition)}`;
NewCommandClass.docLink = `${NewCommandClass.actionDefinition.topicName}:${NewCommandClass.actionDefinition.commandName}`;
TwilioApiCommand.setUpNewCommandClass(NewCommandClass);

return NewCommandClass;
Expand Down Expand Up @@ -123,6 +135,19 @@ describe('base-commands', () => {
expect(Object.keys(NewCommandClass.flags)).to.include('no-limit');
});

test.it('checks the help document url', () => {
const NewCommandClass = getCommandClass(callCreateActionDefinition);
expect(NewCommandClass.id).to.equal('api:core:calls:create');
expect(NewCommandClass.description).to.equal(fakeResource.actions.create.description);
expect(NewCommandClass.docLink).to.equal('https://twilio.com/docs/usage/api');
});

test.it('checks the help document url', () => {
const NewCommandClass = getCommandClass(callApiHelpDoc);
expect(NewCommandClass.id).to.equal('api:sync:services');
expect(NewCommandClass.docLink).to.equal('https://twilio.com/docs/sync/api');
});

test.it('handles remove action', () => {
const NewCommandClass = getCommandClass(callRemoveActionDefinition);

Expand Down

0 comments on commit 37a857d

Please sign in to comment.