Skip to content

Commit

Permalink
fix: update ProfileExplorer to allow "editing" of aspects of the profile
Browse files Browse the repository at this point in the history
This PR also leverages the "interactive for" feature in madwizard 1.1.0
  • Loading branch information
starpit committed Sep 28, 2022
1 parent 47a5348 commit 0b7eb6f
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 30 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions plugins/plugin-codeflare/src/components/DashboardSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

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

type Props = {
selectedProfile?: string
Expand Down Expand Up @@ -62,7 +62,8 @@ export default class DashboardSelect extends React.PureComponent<Props, State> {
public render() {
return (
<Select
variant={SelectVariant.single}
variant="single"
direction="up"
placeholderText="Dashboards"
aria-label="Dashboards selector"
onToggle={this._dashboardSelectOnToggle}
Expand Down
54 changes: 47 additions & 7 deletions plugins/plugin-codeflare/src/components/ProfileExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import React from "react"
import { Profiles } from "madwizard"
import { Loading } from "@kui-shell/plugin-client-common"
import { Icons, Loading, Tooltip } from "@kui-shell/plugin-client-common"
import {
Card,
CardActions,
Expand Down Expand Up @@ -50,6 +50,7 @@ import "../../web/scss/components/ProfileExplorer/_index.scss"

type Props = {
onSelectProfile?(profile: string): void
onSelectGuidebook?(guidebook: string): void
}

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

/** */
type TreeViewDataItemWithChildren = TreeViewDataItem & Required<Pick<TreeViewDataItem, "children">>

export default class ProfileExplorer extends React.PureComponent<Props, State> {
public constructor(props: Props) {
super(props)
Expand Down Expand Up @@ -176,6 +180,7 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
profile={this.state.selectedProfile}
profiles={this.state.profiles}
onSelectProfile={this._handleProfileSelection}
onSelectGuidebook={this.props.onSelectGuidebook}
profileReadiness={this.state.statusWatcher?.readiness}
profileStatus={this.state.statusWatcher}
/>
Expand All @@ -185,7 +190,7 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
}
}

type ProfileCardProps = {
type ProfileCardProps = Pick<Props, "onSelectGuidebook"> & {
profile: string
profiles: Profiles.Profile[]
onSelectProfile: (profile: string) => void
Expand Down Expand Up @@ -274,7 +279,7 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
title: "Cluster",
group: this.groups.Compute,
},
"kubernetes/choose/ns-with-context": {
"kubernetes/choose/ns": {
title: "Namespace",
group: this.groups.Compute,
},
Expand All @@ -285,7 +290,7 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
<ChipGroup numChips={10}>
{Object.entries(form).map(([title, name]) => (
<Chip key={title} isReadOnly textMaxWidth="25ch">
<span className="slightly-deemphasize">{title}</span> <span className="semi-bold color-base0F">{name}</span>
<span className="slightly-deemphasize">{title}</span> <span className="semi-bold color-base0D">{name}</span>
</Chip>
))}
</ChipGroup>
Expand All @@ -308,6 +313,35 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
}
}

private readonly onEdit = (evt: React.MouseEvent) => {
const guidebook = evt.currentTarget.getAttribute("data-guidebook")
if (guidebook) {
if (this.props.onSelectGuidebook) {
this.props.onSelectGuidebook(guidebook)
}
} else {
console.error("Missing guidebook attribute")
}
}

private editable<T extends TreeViewDataItem>(guidebook: string, node: T) {
return Object.assign(node, {
action: (
<Tooltip markdown={`### Update\n#### ${guidebook}\n\nClick to update this choice`}>
<Button
variant="plain"
aria-label="Edit"
data-guidebook={guidebook}
onClick={this.onEdit}
className="codeflare--profile-explorer-edit-button"
>
<Icons icon="Edit" />
</Button>
</Tooltip>
),
})
}

private body() {
// TODO: Retrieve real data and abstract to its own component
const profile = this.props.profiles.find((_) => _.name === this.props.profile)
Expand All @@ -328,13 +362,19 @@ class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState
}
const { children } = groups[meta.group.title]

children.push(this.treeNode(meta, value))
children.push(this.editable(title, this.treeNode(meta, value)))
}

return groups
}, {} as Record<string, TreeViewDataItem & Required<Pick<TreeViewDataItem, "children">>>)
}, {} as Record<string, TreeViewDataItemWithChildren>)

const data = Object.values(tree)
if (data.length === 0) {
// oops, this profile has no "shape", no choices have been
// made for us to visualize
data.push({ name: "Empty", children: [] })
}

return <TreeView hasGuides defaultAllExpanded data={data} variant="compactNoBackground" />
}

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

public render() {
return (
<Card className="top-pad left-pad right-pad bottompad" isSelectableRaised isSelected>
<Card className="top-pad left-pad right-pad bottom-pad" isSelectableRaised isSelected>
<CardHeader>
<CardTitle>{this.title()}</CardTitle>
<CardActions hasNoOffset>{this.actions()}</CardActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ export default class SelectedProfileTerminal extends React.PureComponent<MyProps
}

public render() {
return <RestartableTerminal key={this.state.cmdline} {...this.props} cmdline={this.state.cmdline} />
return <RestartableTerminal {...this.props} cmdline={this.state.cmdline} />
}
}
37 changes: 30 additions & 7 deletions plugins/plugin-codeflare/src/controller/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,23 @@ export async function shell(args: Arguments) {
}

type Props = Pick<BaseProps, "tab" | "repl">
type State = Partial<Pick<BaseProps, "cmdline" | "env">> & { error?: boolean; selectedProfile?: string }
type State = Partial<Pick<BaseProps, "cmdline" | "env">> & {
/** Internal error in rendering */
error?: boolean

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

/** Use this profile in the terminal execution */
selectedProfile?: string
}

class TaskTerminal extends React.PureComponent<Props, State> {
/** Default guidebook to show in the terminal */
private readonly defaultGuidebook = "ml/codeflare"

/** Allotment initial split sizes */
private readonly sizes = [35, 65]
private readonly sizes = [40, 60]

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

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

private async init() {
private async init(guidebook?: string) {
try {
// respawn, meaning launch it with codeflare
const { argv, env } = await respawn(this.tasks[0].argv)
const cmdline = argv.map((_) => encodeComponent(_)).join(" ")
const cmdline = [
...argv.map((_) => encodeComponent(_)),
guidebook || this.defaultGuidebook,
...(guidebook ? ["--ifor", guidebook] : []),
]
.filter(Boolean)
.join(" ")

this.setState({
cmdline,
env,
})
} catch (error) {
console.error("Error initializing command line", error)
this.state = {
this.setState({
error: true,
}
})
}
}

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

/** Event handler for switching to a different guidebook */
private readonly onSelectGuidebook = (guidebook: string) => this.init(guidebook)

public static getDerivedStateFromError() {
return { error: true }
}
Expand All @@ -94,7 +117,7 @@ class TaskTerminal extends React.PureComponent<Props, State> {
return (
<Allotment defaultSizes={this.sizes} snap>
<Allotment.Pane className="flex-fill flex-layout flex-align-stretch" minSize={400}>
<ProfileExplorer onSelectProfile={this.onSelectProfile} />
<ProfileExplorer onSelectProfile={this.onSelectProfile} onSelectGuidebook={this.onSelectGuidebook} />
</Allotment.Pane>
<Allotment.Pane className="flex-fill flex-layout flex-align-stretch">
{!this.state.selectedProfile ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
}
}

@mixin EditButton {
button.codeflare--profile-explorer-edit-button {
@content;
}
}

@include ProfileExplorer {
display: flex;
.pf-c-card__body {
Expand All @@ -34,8 +40,17 @@

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

button {
color: var(--color-text-01) !important;
.pf-c-card .pf-c-tree-view button.pf-c-tree-view__node {
color: var(--color-text-01);
}

.pf-c-tree-view {
@include EditButton {
color: var(--color-base04);
&:hover {
color: var(--color-text-01);
}
}
}

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

.pf-c-tree-view__content {
/* so that action items align top */
align-items: flex-start;
}

.pf-c-tree-view__node-text {
color: var(--color-text-02);
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/plugin-madwizard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"access": "public"
},
"dependencies": {
"madwizard": "^1.0.3",
"madwizard": "^1.1.0",
"@guidebooks/store": "^0.14.1"
}
}
11 changes: 9 additions & 2 deletions plugins/plugin-madwizard/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export interface Options extends ParsedOptions {
/** Interactive guide mode? [default: false] */
interactive: boolean

/** Interactive guide mode only for last question? [default: false] */
z: boolean

/** Interactive guide mode only for the given notebook? [default: false] */
ifor: string

/** Use team-focused assertions */
team?: string
}
Expand Down Expand Up @@ -140,7 +146,8 @@ export function doMadwizard({ readonlyUI = true, task, withFilepath = true, cb,
profilesPath: parsedOptions["profiles-path"] || parsedOptions.P,
store: parsedOptions.s || process.env.GUIDEBOOK_STORE,
verbose: parsedOptions.V,
interactive: parsedOptions.i || !parsedOptions.y,
ifor: parsedOptions.ifor, // interactive only for a given guidebook?
interactive: parsedOptions.i || (!parsedOptions.ifor && !parsedOptions.y),
assertions: assertionsFn ? assertionsFn(parsedOptions) : undefined,
}
)
Expand All @@ -163,7 +170,7 @@ export function doMadwizard({ readonlyUI = true, task, withFilepath = true, cb,
}

export const flags = {
boolean: ["u", "V", "n", "q", "i", "y"],
boolean: ["u", "V", "n", "q", "i", "y", "z"],
configuration: { "populate--": true },
alias: {
store: ["s"],
Expand Down

0 comments on commit 0b7eb6f

Please sign in to comment.