Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit e903bc7

Browse files
committed
feat: commands should be able to separate resource model fetch from view transformation functions
Fixes #4437
1 parent a7732b6 commit e903bc7

File tree

14 files changed

+102
-92
lines changed

14 files changed

+102
-92
lines changed

packages/core/src/core/command-tree.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -494,20 +494,6 @@ const _read = async <T extends KResponse, O extends ParsedOptions>(
494494
return false
495495
}
496496
} else {
497-
if (leaf.options && leaf.options.requiresFullyQualifiedRoute) {
498-
const routeWithoutContext = `/${originalArgv.join('/')}`
499-
if (leaf.route !== routeWithoutContext) {
500-
// e.g. executing "help" we don't want to use the default
501-
// context (see "subtree help" above for an example use of
502-
// this feature)
503-
if (argv.length === originalArgv.length && argv.every((elt, idx) => elt === originalArgv[idx])) {
504-
return false
505-
} else {
506-
return _read(getModelInternal().root, originalArgv, undefined, originalArgv, tryCatchalls)
507-
}
508-
}
509-
}
510-
511497
return withEvents<T, O>(evaluator, leaf)
512498
}
513499
}

packages/core/src/models/command.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,6 @@ export interface CommandOptions extends CapabilityRequirements {
6868
// hint for screen height in popup mode
6969
height?: number
7070

71-
// request that the REPL be cleared of the initial command in popup mode
72-
clearREPLOnLoad?: boolean
73-
74-
// show this placeholder text when executing the command in popup mode (instead of the command line)
75-
placeholder?: string
76-
7771
/** is this an interior node ("directory", versus a leaf-node with a command handler */
7872
isDirectory?: boolean
7973

@@ -84,8 +78,12 @@ export interface CommandOptions extends CapabilityRequirements {
8478
override?: CommandHandler<KResponse, ParsedOptions>
8579
plugin?: string
8680
okOptions?: string[]
87-
isIntention?: boolean
88-
requiresFullyQualifiedRoute?: boolean
81+
82+
/** model to view transformer */
83+
viewTransformer?<T extends KResponse, O extends ParsedOptions>(
84+
args: EvaluatorArgs<O>,
85+
response: T
86+
): Promise<T> | void | Promise<void>
8987
}
9088

9189
export interface Event {

packages/core/src/models/entity.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,12 @@ export function isRawResponse<Content extends RawContent>(entity: Entity<Content
160160
*/
161161
export type ScalarResponse<RowType extends Row = Row> = SimpleEntity | Table<RowType> | MixedResponse
162162

163+
export type ViewableResponse = MultiModalResponse | NavResponse
164+
163165
export type StructuredResponse<
164166
Content = void,
165167
SomeSortOfResource extends MetadataBearing<Content> = MetadataBearing<Content>
166-
> = MultiModalResponse | NavResponse | UsageModel | SomeSortOfResource | RawResponse<Content>
168+
> = ViewableResponse | UsageModel | SomeSortOfResource | RawResponse<Content>
167169

168170
/**
169171
* A potentially more complex entity with a "spec"

packages/core/src/repl/exec.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { isNavResponse } from '../models/NavResponse'
3535
import {
3636
CommandTreeResolution,
3737
CommandHandlerWithEvents,
38+
EvaluatorArgs as Arguments,
3839
ExecType,
3940
EvaluatorArgs,
4041
KResponse,
@@ -336,21 +337,23 @@ class InProcessExecutor implements Executor {
336337

337338
this.loadSymbolTable(tab, execOptions)
338339

340+
const args: Arguments<O> = {
341+
tab,
342+
REPL,
343+
block: execOptions.block,
344+
nextBlock: undefined,
345+
argv,
346+
command,
347+
execOptions,
348+
argvNoOptions,
349+
parsedOptions: parsedOptions as O,
350+
createOutputStream: execOptions.createOutputStream || (() => this.makeStream(getTabId(tab), execUUID))
351+
}
352+
339353
let response: T | Promise<T>
340354
try {
341355
response = await Promise.resolve(
342-
currentEvaluatorImpl.apply<T, O>(commandUntrimmed, execOptions, evaluator, {
343-
tab,
344-
REPL,
345-
block: execOptions.block,
346-
nextBlock: undefined,
347-
argv,
348-
command,
349-
execOptions,
350-
argvNoOptions,
351-
parsedOptions: parsedOptions as O,
352-
createOutputStream: execOptions.createOutputStream || (() => this.makeStream(getTabId(tab), execUUID))
353-
})
356+
currentEvaluatorImpl.apply<T, O>(commandUntrimmed, execOptions, evaluator, args)
354357
).then(response => {
355358
// indicate that the command was successfuly completed
356359
evaluator.success({
@@ -371,6 +374,13 @@ class InProcessExecutor implements Executor {
371374
response = err
372375
}
373376

377+
if (evaluator.options.viewTransformer) {
378+
response = await Promise.resolve(response).then(async _ => {
379+
const maybeAView = await evaluator.options.viewTransformer(args, _)
380+
return maybeAView || _
381+
})
382+
}
383+
374384
// the || true part is a safeguard for cases where typescript
375385
// didn't catch a command handler returning nothing; it
376386
// shouldn't happen, but probably isn't a sign of a dire

plugins/plugin-kubectl/components/src/CurrentNamespace.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export default class CurrentNamespace extends React.PureComponent<{}, State> {
5252
const currentContext = await getCurrentContext(tab)
5353
eventChannelUnsafe.emit('/kubeui/context/current', currentContext)
5454

55-
if (currentContext.metadata.namespace) {
55+
if (currentContext && currentContext.metadata && currentContext.metadata.namespace) {
5656
this.setState({
5757
text: currentContext === undefined ? '' : this.renderNamespace(currentContext),
5858
viewLevel: 'normal' // only show normally if we succeed; see https://github.com/IBM/kui/issues/3537

plugins/plugin-kubectl/oc/src/controller/kubectl/delegates.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import {
1919
commandPrefix,
2020
defaultFlags,
2121
doCreate,
22-
doGet,
22+
getter,
2323
doDelete,
24-
doDescribe,
24+
describer,
2525
doEdit,
2626
doRun
2727
} from '@kui-shell/plugin-kubectl'
@@ -32,8 +32,9 @@ export default (registrar: Registrar) => {
3232
registrar.listen(`/${commandPrefix}/${command}/apply`, doCreate('apply', command), defaultFlags)
3333
registrar.listen(`/${commandPrefix}/${command}/create`, doCreate('create', command), defaultFlags)
3434
registrar.listen(`/${commandPrefix}/${command}/delete`, doDelete(command), defaultFlags)
35-
registrar.listen(`/${commandPrefix}/${command}/describe`, doDescribe(command), defaultFlags)
3635
registrar.listen(`/${commandPrefix}/${command}/edit`, doEdit(command), defaultFlags)
37-
registrar.listen(`/${commandPrefix}/${command}/get`, doGet(command), defaultFlags)
3836
registrar.listen(`/${commandPrefix}/${command}/run`, doRun(command), defaultFlags)
37+
38+
getter(registrar, command)
39+
describer(registrar, command)
3940
}

plugins/plugin-kubectl/src/controller/kubectl/contexts.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {
18-
i18n,
19-
Tab,
20-
Table,
21-
Row,
22-
RawResponse,
23-
Arguments,
24-
ExecOptions,
25-
Registrar,
26-
UsageModel,
27-
KResponse
28-
} from '@kui-shell/core'
17+
import { i18n, Tab, Table, Row, RawResponse, Arguments, Registrar, UsageModel, KResponse } from '@kui-shell/core'
2918

3019
import flags from './flags'
3120
import apiVersion from './apiVersion'
@@ -55,26 +44,17 @@ const usage = {
5544
* Add click handlers to change context
5645
*
5746
*/
58-
const addClickHandlers = (table: Table, { REPL }: Arguments, execOptions: ExecOptions): Table => {
47+
const addClickHandlers = (table: Table, { REPL }: Arguments): Table => {
5948
const body = table.body.map(row => {
6049
const nameAttr = row.attributes.find(({ key }) => key === 'NAME')
6150
const { value: contextName } = nameAttr
6251

63-
nameAttr.outerCSS += ' entity-name-group-narrow'
64-
6552
const onclick = async () => {
66-
await REPL.qexec(
67-
`kubectl config use-context ${REPL.encodeComponent(contextName)}`,
68-
undefined,
69-
undefined,
70-
Object.assign({}, execOptions, { raw: true })
71-
)
72-
row.setSelected()
53+
await REPL.pexec(`kubectl config use-context ${REPL.encodeComponent(contextName)}`)
7354
}
7455

7556
row.name = contextName
7657
row.onclick = onclick
77-
nameAttr.onclick = onclick
7858

7959
return row
8060
})
@@ -134,7 +114,7 @@ const listContexts = async (args: Arguments): Promise<RawResponse<KubeContext[]>
134114
}))
135115
}
136116
} else {
137-
return addClickHandlers(contexts, args, execOptions)
117+
return addClickHandlers(contexts, args)
138118
}
139119
}
140120

plugins/plugin-kubectl/src/controller/kubectl/describe.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616

1717
import { Arguments, Registrar, KResponse } from '@kui-shell/core'
1818

19-
import flags from './flags'
2019
import { exec } from './exec'
2120
import commandPrefix from '../command-prefix'
2221

23-
import { doGetAsEntity } from './get'
22+
import { doGetAsEntity, getFlags as flags } from './get'
2423
import { KubeOptions } from './options'
2524
import { isUsage, doHelp } from '../../lib/util/help'
2625
import { commandWithoutResource } from '../../lib/util/util'
@@ -33,7 +32,7 @@ function prepareArgsForDescribe(args: Arguments<KubeOptions>) {
3332
return `${args.command.replace(/(k|kubectl|oc)(\s+)describe(\s+)/, '$1$2get$3')} -o yaml`
3433
}
3534

36-
export const doDescribe = (command = 'kubectl') =>
35+
const doDescribe = (command: string) =>
3736
async function(args: Arguments<KubeOptions>): Promise<KResponse> {
3837
if (isUsage(args)) {
3938
return doHelp(command, args)
@@ -46,8 +45,12 @@ export const doDescribe = (command = 'kubectl') =>
4645
}
4746
}
4847

48+
/** Register a command listener */
49+
export function describer(registrar: Registrar, command: string, cli = command) {
50+
registrar.listen(`/${commandPrefix}/${command}/describe`, doDescribe(cli), flags)
51+
}
52+
4953
export default (registrar: Registrar) => {
50-
const handler = doDescribe()
51-
registrar.listen(`/${commandPrefix}/kubectl/describe`, handler, flags)
52-
registrar.listen(`/${commandPrefix}/k/describe`, handler, flags)
54+
describer(registrar, 'kubectl')
55+
describer(registrar, 'k', 'kubectl')
5356
}

plugins/plugin-kubectl/src/controller/kubectl/flags.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { CommandOptions } from '@kui-shell/core'
18+
1719
const defaultBooleans = [
1820
'w',
1921
'watch',
@@ -29,7 +31,7 @@ const defaultBooleans = [
2931
'show-labels'
3032
]
3133

32-
export function flags(booleans: string[] = []) {
34+
export function flags(booleans: string[] = []): CommandOptions {
3335
return {
3436
flags: {
3537
configuration: {

plugins/plugin-kubectl/src/controller/kubectl/get-namespaces.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
import Debug from 'debug'
1818
import { isTable, Arguments, Registrar, Tab, Table, Row, Cell, ExecType } from '@kui-shell/core'
1919

20-
import flags from './flags'
2120
import { getCurrentContext } from './contexts'
2221
import commandPrefix from '../command-prefix'
23-
import { doGet, doGetAsTable, rawGet } from './get'
22+
import { doGet, doGetAsTable, rawGet, getFlags as flags } from './get'
2423
import { KubeOptions, isTableRequest, isWatchRequest } from './options'
2524

2625
const debug = Debug('plugin-kubectl/controller/kubectl/get/namespace')

0 commit comments

Comments
 (0)