Skip to content

Commit e21fcce

Browse files
committed
feat: update terminal componetry to support onExit execution of custom guidebooks
1 parent 63f1636 commit e21fcce

File tree

3 files changed

+75
-23
lines changed

3 files changed

+75
-23
lines changed

Diff for: plugins/plugin-codeflare/src/components/RestartableTerminal.tsx

+16-8
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ function watch(stream: PassThrough, job: Job) {
3434
}
3535
}
3636

37-
export type Props = {
37+
export type Props = Pick<Arguments, "tab" | "REPL"> & {
38+
/** Execute this command line */
3839
cmdline: string
40+
41+
/** Execute the given `cmdline` with this set of environment variables */
3942
env: Record<string, string>
40-
tab: Arguments["tab"]
41-
repl: Arguments["REPL"]
43+
44+
/** Callback when the underlying PTY exits */
45+
onExit?(code: number): void
4246
}
4347

4448
type State = {
@@ -65,15 +69,19 @@ export default class RestartableTerminal extends React.PureComponent<Props, Stat
6569
// component, which expects something stream-like
6670
const passthrough = new PassThrough()
6771

68-
await this.props.repl.qexec(this.props.cmdline, undefined, undefined, {
72+
await this.props.REPL.qexec(this.props.cmdline, undefined, undefined, {
6973
tab: this.props.tab,
7074
env: this.props.env,
7175
quiet: true, // strange i know, but this forces PTY execution
72-
onExit: async () => {
76+
onExit: async (code: number) => {
7377
if (this.mounted) {
74-
// restart, if still mounted
75-
await new Promise((resolve) => setTimeout(resolve, 50000))
76-
this.initPty()
78+
if (this.props.onExit) {
79+
this.props.onExit(code)
80+
} else {
81+
// restart, if still mounted
82+
await new Promise((resolve) => setTimeout(resolve, 60 * 1000))
83+
this.initPty()
84+
}
7785
}
7886
},
7987
onInit: () => (_) => {

Diff for: plugins/plugin-codeflare/src/controller/terminal.tsx

+58-14
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import React from "react"
1818
import { Allotment } from "allotment"
1919
import { Loading } from "@kui-shell/plugin-client-common"
2020
import { Arguments, encodeComponent } from "@kui-shell/core"
21-
import { defaultGuidebook } from "@kui-shell/client/config.d/client.json"
21+
import { defaultGuidebook as defaultGuidebookFromClient } from "@kui-shell/client/config.d/client.json"
2222

2323
import respawn from "./respawn"
2424

@@ -50,11 +50,17 @@ export async function shell(args: Arguments) {
5050
const cmdline = argv.map((_) => encodeComponent(_)).join(" ")
5151

5252
return {
53-
react: <Terminal cmdline={cmdline} env={env} repl={args.REPL} tab={args.tab} />,
53+
react: <Terminal cmdline={cmdline} env={env} REPL={args.REPL} tab={args.tab} />,
5454
}
5555
}
5656

57-
type Props = Pick<BaseProps, "tab" | "repl"> & {
57+
export type Props = Pick<BaseProps, "tab" | "REPL" | "onExit"> & {
58+
/** Default guidebook (if not given, we will take the value from the client definition) */
59+
defaultGuidebook?: string
60+
61+
/** Run guidebook in non-interactive mode? */
62+
defaultNoninteractive?: boolean
63+
5864
/** Callback when user selects a profile */
5965
onSelectProfile?(profile: string, profiles?: import("madwizard").Profiles.Profile[]): void
6066

@@ -63,12 +69,21 @@ type Props = Pick<BaseProps, "tab" | "repl"> & {
6369
}
6470

6571
type State = Partial<Pick<BaseProps, "cmdline" | "env">> & {
72+
/** Number of times we have called this.init() */
73+
initCount: number
74+
6675
/** Internal error in rendering */
6776
error?: boolean
6877

6978
/** Use this guidebook in the terminal execution */
7079
guidebook?: string
7180

81+
/** Run guidebook in non-interactive mode? */
82+
noninteractive?: boolean
83+
84+
/** Interactive only for the given guidebook? */
85+
ifor?: boolean
86+
7287
/** Use this profile in the terminal execution */
7388
selectedProfile?: string
7489
}
@@ -86,31 +101,41 @@ export class TaskTerminal extends React.PureComponent<Props, State> {
86101
public constructor(props: Props) {
87102
super(props)
88103

104+
this.state = { initCount: 0, guidebook: defaultGuidebookFromClient }
89105
this.init()
90-
this.state = {}
91106
}
92107

93-
private async init(guidebook?: string) {
108+
/**
109+
* Initialize for a new guidebook execution. Which guidebook depends
110+
* on: if as given, then as given in props, then as given in
111+
* client.
112+
*/
113+
private async init() {
114+
const guidebook = this.state.guidebook
115+
94116
try {
95117
// respawn, meaning launch it with codeflare
96118
const { argv, env } = await respawn(this.tasks[0].argv)
97119
const cmdline = [
98120
...argv.map((_) => encodeComponent(_)),
99-
guidebook || defaultGuidebook,
100-
...(guidebook ? ["--ifor", guidebook] : []),
121+
guidebook,
122+
...(this.state.noninteractive ? ["--y"] : []),
123+
...(this.state.ifor ? ["--ifor", guidebook] : []),
101124
]
102125
.filter(Boolean)
103126
.join(" ")
104127

105-
this.setState({
128+
this.setState((curState) => ({
106129
cmdline,
130+
initCount: curState.initCount + 1,
107131
env: Object.assign({}, env, { MWCLEAR_INITIAL: "true" }),
108-
})
132+
}))
109133
} catch (error) {
110134
console.error("Error initializing command line", error)
111-
this.setState({
135+
this.setState((curState) => ({
112136
error: true,
113-
})
137+
initCount: curState.initCount + 1,
138+
}))
114139
}
115140
}
116141

@@ -124,7 +149,20 @@ export class TaskTerminal extends React.PureComponent<Props, State> {
124149
}
125150

126151
/** Event handler for switching to a different guidebook */
127-
private readonly onSelectGuidebook = (guidebook: string) => this.init(guidebook)
152+
private readonly onSelectGuidebook = (guidebook: string) =>
153+
this.setState({ guidebook, ifor: true, noninteractive: false })
154+
155+
public static getDerivedStateFromProps(props: Props, state: State) {
156+
if (props.defaultGuidebook && state.guidebook !== props.defaultGuidebook) {
157+
return {
158+
ifor: false,
159+
guidebook: props.defaultGuidebook,
160+
noninteractive: props.defaultNoninteractive,
161+
}
162+
}
163+
164+
return
165+
}
128166

129167
public static getDerivedStateFromError() {
130168
return { error: true }
@@ -133,6 +171,12 @@ export class TaskTerminal extends React.PureComponent<Props, State> {
133171
console.error("catastrophic error", error, errorInfo)
134172
}
135173

174+
public componentDidUpdate(prevProps: Props, prevState: State) {
175+
if (prevState.guidebook !== this.state.guidebook || prevState.ifor !== this.state.ifor) {
176+
this.init()
177+
}
178+
}
179+
136180
public render() {
137181
if (this.state.error) {
138182
return "Internal Error"
@@ -156,7 +200,7 @@ export class TaskTerminal extends React.PureComponent<Props, State> {
156200
>
157201
<AllotmentFillPane>
158202
<SelectedProfileTerminal
159-
key={this.state.cmdline + "-" + this.state.selectedProfile}
203+
key={this.state.initCount + "_" + this.state.cmdline + "-" + this.state.selectedProfile}
160204
cmdline={this.state.cmdline}
161205
env={this.state.env}
162206
{...this.props}
@@ -176,6 +220,6 @@ export class TaskTerminal extends React.PureComponent<Props, State> {
176220
* This is a command handler that opens up a terminal to run a selected profile-oriented task */
177221
export function task(args: Arguments) {
178222
return {
179-
react: <TaskTerminal repl={args.REPL} tab={args.tab} />,
223+
react: <TaskTerminal REPL={args.REPL} tab={args.tab} />,
180224
}
181225
}

Diff for: plugins/plugin-codeflare/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616

1717
/* Your exported API */
1818

19-
export { TaskTerminal as WorkloadDesigner } from "./controller/terminal"
19+
export { TaskTerminal as WorkloadDesigner, Props as WorkloadDesignerProps } from "./controller/terminal"

0 commit comments

Comments
 (0)