Skip to content

Commit 0b7eb6f

Browse files
committed
fix: update ProfileExplorer to allow "editing" of aspects of the profile
This PR also leverages the "interactive for" feature in madwizard 1.1.0
1 parent 47a5348 commit 0b7eb6f

File tree

8 files changed

+121
-30
lines changed

8 files changed

+121
-30
lines changed

Diff for: package-lock.json

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import React from "react"
1818
import { openWindow } from "../controller/profile/actions"
19-
import { Select, SelectOption, SelectOptionObject, SelectVariant } from "@patternfly/react-core"
19+
import { Select, SelectOption, SelectOptionObject } from "@patternfly/react-core"
2020

2121
type Props = {
2222
selectedProfile?: string
@@ -62,7 +62,8 @@ export default class DashboardSelect extends React.PureComponent<Props, State> {
6262
public render() {
6363
return (
6464
<Select
65-
variant={SelectVariant.single}
65+
variant="single"
66+
direction="up"
6667
placeholderText="Dashboards"
6768
aria-label="Dashboards selector"
6869
onToggle={this._dashboardSelectOnToggle}

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

+47-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import React from "react"
1818
import { Profiles } from "madwizard"
19-
import { Loading } from "@kui-shell/plugin-client-common"
19+
import { Icons, Loading, Tooltip } from "@kui-shell/plugin-client-common"
2020
import {
2121
Card,
2222
CardActions,
@@ -50,6 +50,7 @@ import "../../web/scss/components/ProfileExplorer/_index.scss"
5050

5151
type Props = {
5252
onSelectProfile?(profile: string): void
53+
onSelectGuidebook?(guidebook: string): void
5354
}
5455

5556
type State = {
@@ -68,6 +69,9 @@ type Group = { title: string; name?: string }
6869
/** Metadata for tree node */
6970
type Metadata = { title: string; group: Group }
7071

72+
/** */
73+
type TreeViewDataItemWithChildren = TreeViewDataItem & Required<Pick<TreeViewDataItem, "children">>
74+
7175
export default class ProfileExplorer extends React.PureComponent<Props, State> {
7276
public constructor(props: Props) {
7377
super(props)
@@ -176,6 +180,7 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
176180
profile={this.state.selectedProfile}
177181
profiles={this.state.profiles}
178182
onSelectProfile={this._handleProfileSelection}
183+
onSelectGuidebook={this.props.onSelectGuidebook}
179184
profileReadiness={this.state.statusWatcher?.readiness}
180185
profileStatus={this.state.statusWatcher}
181186
/>
@@ -185,7 +190,7 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
185190
}
186191
}
187192

188-
type ProfileCardProps = {
193+
type ProfileCardProps = Pick<Props, "onSelectGuidebook"> & {
189194
profile: string
190195
profiles: Profiles.Profile[]
191196
onSelectProfile: (profile: string) => void
@@ -274,7 +279,7 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
274279
title: "Cluster",
275280
group: this.groups.Compute,
276281
},
277-
"kubernetes/choose/ns-with-context": {
282+
"kubernetes/choose/ns": {
278283
title: "Namespace",
279284
group: this.groups.Compute,
280285
},
@@ -285,7 +290,7 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
285290
<ChipGroup numChips={10}>
286291
{Object.entries(form).map(([title, name]) => (
287292
<Chip key={title} isReadOnly textMaxWidth="25ch">
288-
<span className="slightly-deemphasize">{title}</span> <span className="semi-bold color-base0F">{name}</span>
293+
<span className="slightly-deemphasize">{title}</span> <span className="semi-bold color-base0D">{name}</span>
289294
</Chip>
290295
))}
291296
</ChipGroup>
@@ -308,6 +313,35 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
308313
}
309314
}
310315

316+
private readonly onEdit = (evt: React.MouseEvent) => {
317+
const guidebook = evt.currentTarget.getAttribute("data-guidebook")
318+
if (guidebook) {
319+
if (this.props.onSelectGuidebook) {
320+
this.props.onSelectGuidebook(guidebook)
321+
}
322+
} else {
323+
console.error("Missing guidebook attribute")
324+
}
325+
}
326+
327+
private editable<T extends TreeViewDataItem>(guidebook: string, node: T) {
328+
return Object.assign(node, {
329+
action: (
330+
<Tooltip markdown={`### Update\n#### ${guidebook}\n\nClick to update this choice`}>
331+
<Button
332+
variant="plain"
333+
aria-label="Edit"
334+
data-guidebook={guidebook}
335+
onClick={this.onEdit}
336+
className="codeflare--profile-explorer-edit-button"
337+
>
338+
<Icons icon="Edit" />
339+
</Button>
340+
</Tooltip>
341+
),
342+
})
343+
}
344+
311345
private body() {
312346
// TODO: Retrieve real data and abstract to its own component
313347
const profile = this.props.profiles.find((_) => _.name === this.props.profile)
@@ -328,13 +362,19 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
328362
}
329363
const { children } = groups[meta.group.title]
330364

331-
children.push(this.treeNode(meta, value))
365+
children.push(this.editable(title, this.treeNode(meta, value)))
332366
}
333367

334368
return groups
335-
}, {} as Record<string, TreeViewDataItem & Required<Pick<TreeViewDataItem, "children">>>)
369+
}, {} as Record<string, TreeViewDataItemWithChildren>)
336370

337371
const data = Object.values(tree)
372+
if (data.length === 0) {
373+
// oops, this profile has no "shape", no choices have been
374+
// made for us to visualize
375+
data.push({ name: "Empty", children: [] })
376+
}
377+
338378
return <TreeView hasGuides defaultAllExpanded data={data} variant="compactNoBackground" />
339379
}
340380

@@ -366,7 +406,7 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
366406

367407
public render() {
368408
return (
369-
<Card className="top-pad left-pad right-pad bottompad" isSelectableRaised isSelected>
409+
<Card className="top-pad left-pad right-pad bottom-pad" isSelectableRaised isSelected>
370410
<CardHeader>
371411
<CardTitle>{this.title()}</CardTitle>
372412
<CardActions hasNoOffset>{this.actions()}</CardActions>

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ export default class SelectedProfileTerminal extends React.PureComponent<MyProps
4141
}
4242

4343
public render() {
44-
return <RestartableTerminal key={this.state.cmdline} {...this.props} cmdline={this.state.cmdline} />
44+
return <RestartableTerminal {...this.props} cmdline={this.state.cmdline} />
4545
}
4646
}

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

+30-7
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,23 @@ export async function shell(args: Arguments) {
4343
}
4444

4545
type Props = Pick<BaseProps, "tab" | "repl">
46-
type State = Partial<Pick<BaseProps, "cmdline" | "env">> & { error?: boolean; selectedProfile?: string }
46+
type State = Partial<Pick<BaseProps, "cmdline" | "env">> & {
47+
/** Internal error in rendering */
48+
error?: boolean
49+
50+
/** Use this guidebook in the terminal execution */
51+
guidebook?: string
52+
53+
/** Use this profile in the terminal execution */
54+
selectedProfile?: string
55+
}
56+
4757
class TaskTerminal extends React.PureComponent<Props, State> {
58+
/** Default guidebook to show in the terminal */
59+
private readonly defaultGuidebook = "ml/codeflare"
60+
4861
/** Allotment initial split sizes */
49-
private readonly sizes = [35, 65]
62+
private readonly sizes = [40, 60]
5063

5164
private readonly tasks = [{ label: "Run a Job", argv: ["codeflare", "-p", "${SELECTED_PROFILE}"] }]
5265

@@ -57,26 +70,36 @@ class TaskTerminal extends React.PureComponent<Props, State> {
5770
this.state = {}
5871
}
5972

60-
private async init() {
73+
private async init(guidebook?: string) {
6174
try {
6275
// respawn, meaning launch it with codeflare
6376
const { argv, env } = await respawn(this.tasks[0].argv)
64-
const cmdline = argv.map((_) => encodeComponent(_)).join(" ")
77+
const cmdline = [
78+
...argv.map((_) => encodeComponent(_)),
79+
guidebook || this.defaultGuidebook,
80+
...(guidebook ? ["--ifor", guidebook] : []),
81+
]
82+
.filter(Boolean)
83+
.join(" ")
6584

6685
this.setState({
6786
cmdline,
6887
env,
6988
})
7089
} catch (error) {
7190
console.error("Error initializing command line", error)
72-
this.state = {
91+
this.setState({
7392
error: true,
74-
}
93+
})
7594
}
7695
}
7796

97+
/** Event handler for switching to a different profile */
7898
private readonly onSelectProfile = (selectedProfile: string) => this.setState({ selectedProfile })
7999

100+
/** Event handler for switching to a different guidebook */
101+
private readonly onSelectGuidebook = (guidebook: string) => this.init(guidebook)
102+
80103
public static getDerivedStateFromError() {
81104
return { error: true }
82105
}
@@ -94,7 +117,7 @@ class TaskTerminal extends React.PureComponent<Props, State> {
94117
return (
95118
<Allotment defaultSizes={this.sizes} snap>
96119
<Allotment.Pane className="flex-fill flex-layout flex-align-stretch" minSize={400}>
97-
<ProfileExplorer onSelectProfile={this.onSelectProfile} />
120+
<ProfileExplorer onSelectProfile={this.onSelectProfile} onSelectGuidebook={this.onSelectGuidebook} />
98121
</Allotment.Pane>
99122
<Allotment.Pane className="flex-fill flex-layout flex-align-stretch">
100123
{!this.state.selectedProfile ? (

Diff for: plugins/plugin-codeflare/web/scss/components/ProfileExplorer/_index.scss

+22-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
}
2727
}
2828

29+
@mixin EditButton {
30+
button.codeflare--profile-explorer-edit-button {
31+
@content;
32+
}
33+
}
34+
2935
@include ProfileExplorer {
3036
display: flex;
3137
.pf-c-card__body {
@@ -34,8 +40,17 @@
3440

3541
font-family: var(--font-sans-serif);
3642

37-
button {
38-
color: var(--color-text-01) !important;
43+
.pf-c-card .pf-c-tree-view button.pf-c-tree-view__node {
44+
color: var(--color-text-01);
45+
}
46+
47+
.pf-c-tree-view {
48+
@include EditButton {
49+
color: var(--color-base04);
50+
&:hover {
51+
color: var(--color-text-01);
52+
}
53+
}
3954
}
4055

4156
.pf-c-tree-view {
@@ -47,6 +62,11 @@
4762
--pf-c-tree-view--m-compact__node-container--nested--PaddingLeft: 1.5em;
4863
}
4964

65+
.pf-c-tree-view__content {
66+
/* so that action items align top */
67+
align-items: flex-start;
68+
}
69+
5070
.pf-c-tree-view__node-text {
5171
color: var(--color-text-02);
5272
}

Diff for: plugins/plugin-madwizard/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"access": "public"
2424
},
2525
"dependencies": {
26-
"madwizard": "^1.0.3",
26+
"madwizard": "^1.1.0",
2727
"@guidebooks/store": "^0.14.1"
2828
}
2929
}

Diff for: plugins/plugin-madwizard/src/plugin.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ export interface Options extends ParsedOptions {
6565
/** Interactive guide mode? [default: false] */
6666
interactive: boolean
6767

68+
/** Interactive guide mode only for last question? [default: false] */
69+
z: boolean
70+
71+
/** Interactive guide mode only for the given notebook? [default: false] */
72+
ifor: string
73+
6874
/** Use team-focused assertions */
6975
team?: string
7076
}
@@ -140,7 +146,8 @@ export function doMadwizard({ readonlyUI = true, task, withFilepath = true, cb,
140146
profilesPath: parsedOptions["profiles-path"] || parsedOptions.P,
141147
store: parsedOptions.s || process.env.GUIDEBOOK_STORE,
142148
verbose: parsedOptions.V,
143-
interactive: parsedOptions.i || !parsedOptions.y,
149+
ifor: parsedOptions.ifor, // interactive only for a given guidebook?
150+
interactive: parsedOptions.i || (!parsedOptions.ifor && !parsedOptions.y),
144151
assertions: assertionsFn ? assertionsFn(parsedOptions) : undefined,
145152
}
146153
)
@@ -163,7 +170,7 @@ export function doMadwizard({ readonlyUI = true, task, withFilepath = true, cb,
163170
}
164171

165172
export const flags = {
166-
boolean: ["u", "V", "n", "q", "i", "y"],
173+
boolean: ["u", "V", "n", "q", "i", "y", "z"],
167174
configuration: { "populate--": true },
168175
alias: {
169176
store: ["s"],

0 commit comments

Comments
 (0)