From 639ca32d17e523db58d52bf97cd29e4afd471331 Mon Sep 17 00:00:00 2001 From: Nick Mitchell Date: Tue, 21 Apr 2020 18:16:48 -0400 Subject: [PATCH] feat(plugins/plugin-kubectl): improve odo usage experience Fixes #4345 --- .../plugin-kubectl/i18n/resources_en_US.json | 2 + .../plugin-kubectl/oc/src/controller/exec.ts | 5 +- .../oc/src/controller/kubectl/catchall.ts | 44 +++++++++++++---- .../plugin-kubectl/oc/src/controller/raw.ts | 1 + .../controller/kubectl/exec-to-markdown.ts | 49 +++++++++++++++++++ plugins/plugin-kubectl/src/index.ts | 2 + plugins/plugin-kubectl/src/lib/util/help.ts | 22 +++++++-- plugins/plugin-kubectl/src/test/k8s2/usage.ts | 16 ++++++ tools/travis/microk8s.sh | 5 ++ 9 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 plugins/plugin-kubectl/src/controller/kubectl/exec-to-markdown.ts diff --git a/plugins/plugin-kubectl/i18n/resources_en_US.json b/plugins/plugin-kubectl/i18n/resources_en_US.json index d724148a30d..3b06c64254a 100644 --- a/plugins/plugin-kubectl/i18n/resources_en_US.json +++ b/plugins/plugin-kubectl/i18n/resources_en_US.json @@ -36,7 +36,9 @@ "currentContext": "This is your current context", "notCurrentContext": "This is not your current context", "More Information": "More Information", + "Basic": "Basic", "Introduction": "Introduction", + "Miscellaneous": "Miscellaneous", "Options": "Options", "Commands": "Commands", "Examples": "Examples" diff --git a/plugins/plugin-kubectl/oc/src/controller/exec.ts b/plugins/plugin-kubectl/oc/src/controller/exec.ts index a1a69087cc6..e30b893ab9d 100644 --- a/plugins/plugin-kubectl/oc/src/controller/exec.ts +++ b/plugins/plugin-kubectl/oc/src/controller/exec.ts @@ -15,8 +15,9 @@ */ import { Arguments } from '@kui-shell/core' -import { doExecWithStdout, KubeOptions } from '@kui-shell/plugin-kubectl' +import { doExecWithStdout, KubeOptions, commandPrefix } from '@kui-shell/plugin-kubectl' export default function doExec(args: Arguments) { - return doExecWithStdout(args, undefined, 'oc') + const cmd = args.argv[0] === commandPrefix ? args.argv[1] : args.argv[0] + return doExecWithStdout(args, undefined, cmd) } diff --git a/plugins/plugin-kubectl/oc/src/controller/kubectl/catchall.ts b/plugins/plugin-kubectl/oc/src/controller/kubectl/catchall.ts index a4f6ccd34cd..25bd53bbd5f 100644 --- a/plugins/plugin-kubectl/oc/src/controller/kubectl/catchall.ts +++ b/plugins/plugin-kubectl/oc/src/controller/kubectl/catchall.ts @@ -14,11 +14,20 @@ * limitations under the License. */ -import { inBrowser, hasProxy, Arguments, Registrar } from '@kui-shell/core' -import { doExecWithPty, commandPrefix, isUsage, doHelp, KubeOptions } from '@kui-shell/plugin-kubectl' +import { inBrowser, hasProxy, Arguments, Registrar, i18n } from '@kui-shell/core' +import { + doExecWithPty, + doExecWithMarkdown, + commandPrefix, + isUsage, + doHelp, + KubeOptions +} from '@kui-shell/plugin-kubectl' -/** is the given string `str` the `oc` command? */ -const isOc = (str: string) => /^oc$/.test(str) +const strings = i18n('plugin-kubectl') + +/** is the given string `str` the `oc` or `odo` command? */ +const isOc = (str: string) => /^(oc|odo)$/.test(str) export default (registrar: Registrar) => { if (inBrowser() && !hasProxy()) { @@ -34,12 +43,27 @@ export default (registrar: Registrar) => { (argv: string[]) => { return isOc(argv[0]) || (argv[0] === commandPrefix && isOc(argv[1])) }, - (args: Arguments) => - isUsage(args) || - // (args.argv.length === 1 && args.argv[0] === 'oc') || - (args.argv.length === 2 && args.argv[1] === 'oc' && args.argv[0] === commandPrefix) - ? doHelp('oc', args) - : doExecWithPty(args), + async (args: Arguments) => { + const cmd = args.argv[0] === commandPrefix ? args.argv[1] : args.argv[0] + + if (args.argv.length === 1 || (args.argv.length === 2 && args.argv[1] === cmd)) { + // `oc` or `odo` on their own + const response = await doExecWithMarkdown(args, cmd) + response.links.push({ + label: strings('More Information'), + command: `${cmd} -h` + }) + return response + } + + return isUsage(args) + ? doHelp(cmd, args).catch(err => { + // failsafe: something went wrong with doHelp + console.error(err) + return doExecWithPty(args) + }) + : doExecWithPty(args) + }, 1 // priority ) } diff --git a/plugins/plugin-kubectl/oc/src/controller/raw.ts b/plugins/plugin-kubectl/oc/src/controller/raw.ts index c8f41d90163..b0a2b8a3a5c 100644 --- a/plugins/plugin-kubectl/oc/src/controller/raw.ts +++ b/plugins/plugin-kubectl/oc/src/controller/raw.ts @@ -19,4 +19,5 @@ import { doNativeExec, defaultFlags, commandPrefix } from '@kui-shell/plugin-kub export default async (registrar: Registrar) => { registrar.listen(`/${commandPrefix}/_oc`, doNativeExec, Object.assign({}, defaultFlags, { requiresLocal: true })) + registrar.listen(`/${commandPrefix}/_odo`, doNativeExec, Object.assign({}, defaultFlags, { requiresLocal: true })) } diff --git a/plugins/plugin-kubectl/src/controller/kubectl/exec-to-markdown.ts b/plugins/plugin-kubectl/src/controller/kubectl/exec-to-markdown.ts new file mode 100644 index 00000000000..91aa5f75b05 --- /dev/null +++ b/plugins/plugin-kubectl/src/controller/kubectl/exec-to-markdown.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2020 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Arguments, NavResponse, i18n } from '@kui-shell/core' + +import { KubeOptions } from './options' +import { doExecWithStdout } from './exec' +import commandPrefix from '../command-prefix' + +const strings = i18n('plugin-kubectl') + +export default async function(args: Arguments, exec?: string): Promise { + const cmd = args.argv[0] === commandPrefix ? args.argv[1] : args.argv[0] + const raw = await doExecWithStdout(args, undefined, exec) + + const content = raw.replace(/^ (.*)/gm, ' $1') // code blocks: indentation >= 8 + + return { + apiVersion: 'kui-shell/v1', + kind: 'NavResponse', + breadcrumbs: [{ label: cmd }], + links: [], + menus: [ + { + label: strings('Usage'), + items: [ + { + mode: strings('Introduction'), + content, + contentType: 'text/markdown' + } + ] + } + ] + } +} diff --git a/plugins/plugin-kubectl/src/index.ts b/plugins/plugin-kubectl/src/index.ts index ac3a62e7db9..47b4fcaae98 100644 --- a/plugins/plugin-kubectl/src/index.ts +++ b/plugins/plugin-kubectl/src/index.ts @@ -42,6 +42,8 @@ export { default as TrafficLight } from './lib/model/traffic-light' export { default as apiVersion } from './controller/kubectl/apiVersion' +export { default as doExecWithMarkdown } from './controller/kubectl/exec-to-markdown' + export { doExecWithStdoutViaPty, doExecWithPty, diff --git a/plugins/plugin-kubectl/src/lib/util/help.ts b/plugins/plugin-kubectl/src/lib/util/help.ts index aa3a90b783a..1eb757585ee 100644 --- a/plugins/plugin-kubectl/src/lib/util/help.ts +++ b/plugins/plugin-kubectl/src/lib/util/help.ts @@ -130,7 +130,6 @@ const renderHelpUnsafe = ( const processSections = (section: Section) => ({ title: section.title, - nRowsInViewport: section.title.match(/Available Commands/i) ? 8 : undefined, rows: section.content .split(/[\n\r]/) .filter(x => x) @@ -279,7 +278,7 @@ ${usageSection[0].content.slice(0, usageSection[0].content.indexOf('\n')).trim() ] const optionsMenuItems = (): MultiModalMode[] => { - if (verb === '') { + if (verb === '' && command === 'kubectl') { // kubectl return [ { @@ -316,7 +315,7 @@ ${usageSection[0].content.slice(0, usageSection[0].content.indexOf('\n')).trim() .filter(section => /command/i.test(section.title)) .map(section => { return { - mode: section.title.replace(/Command(s)/, '').replace(':', ''), + mode: section.title.replace(/Command(s)/, '').replace(/:$/, '') || strings('Basic'), content: commandDocTable(section.rows, command, verb, 'COMMAND') } }) @@ -324,6 +323,21 @@ ${usageSection[0].content.slice(0, usageSection[0].content.indexOf('\n')).trim() } } + const miscMenu = (): Menu => { + const randomSections = sections.filter( + section => !(/command/i.test(section.title) || /Flags|Options/i.test(section.title)) + ) + if (randomSections.length > 0) { + return { + label: strings('Miscellaneous'), + items: randomSections.map(section => ({ + mode: section.title.replace(/:$/, ''), + content: commandDocTable(section.rows, command, verb, 'COMMAND') + })) + } + } + } + /** header nav contains sections: Examples */ const exampleMenu = () => { if (detailedExample && detailedExample.length > 0) { @@ -341,7 +355,7 @@ ${usageSection[0].content.slice(0, usageSection[0].content.indexOf('\n')).trim() } } - const menus = [headerMenu(strings('Usage')), commandMenu(), exampleMenu()].filter(x => x) + const menus = [headerMenu(strings('Usage')), commandMenu(), miscMenu(), exampleMenu()].filter(x => x) debug('menus', menus) diff --git a/plugins/plugin-kubectl/src/test/k8s2/usage.ts b/plugins/plugin-kubectl/src/test/k8s2/usage.ts index 4cd8ada4ff3..47edb80dbed 100644 --- a/plugins/plugin-kubectl/src/test/k8s2/usage.ts +++ b/plugins/plugin-kubectl/src/test/k8s2/usage.ts @@ -60,4 +60,20 @@ describe('kubectl dash h', function(this: Common.ISuite) { // help on get it('should refresh', () => Common.refresh(this)) help('kubectl get -h', ['kubectl', 'get'], kubectlGetModes) + + // help on oc + it('should refresh', () => Common.refresh(this)) + help('oc', ['oc'], commonModes) + + // oc -h + it('should refresh', () => Common.refresh(this)) + help('oc -h', ['oc'], commonModes.concat(['Basic'])) + + // help on odo + it('should refresh', () => Common.refresh(this)) + help('odo', ['odo'], commonModes) + + // odo -h + it('should refresh', () => Common.refresh(this)) + help('odo -h', ['odo'], commonModes.concat(['Flags', 'Basic'])) }) diff --git a/tools/travis/microk8s.sh b/tools/travis/microk8s.sh index a1ff8486a07..46918dcec15 100755 --- a/tools/travis/microk8s.sh +++ b/tools/travis/microk8s.sh @@ -37,6 +37,11 @@ pushd /tmp sudo cp oc /usr/local/bin sudo chmod +x /usr/local/bin/oc oc version + + echo "Downloading this odo: https://mirror.openshift.com/pub/openshift-v4/clients/odo/latest/odo-${PLATFORM}-amd64" + sudo sh -c "curl -L https://mirror.openshift.com/pub/openshift-v4/clients/odo/latest/odo-${PLATFORM}-amd64 -o /usr/local/bin/odo" + sudo chmod +x /usr/local/bin/odo + odo version fi # wait for the kubectl download and socat installation