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

Commit c685b94

Browse files
committed
feat(plugins/plugin-client-common): assign code blocks an ordinal execution order
This PR also changes the play icon from circle-play to plain play
1 parent 776b4ef commit c685b94

File tree

13 files changed

+452
-144
lines changed

13 files changed

+452
-144
lines changed

packages/test/src/api/Markdown.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export default class Markdown {
1919
public readonly tabs = '.kui--markdown-tabs'
2020
public readonly tab = `${this.tabs} .kui--markdown-tab button`
2121

22+
private readonly _codeBlock = '.kui--code-block-in-markdown'
23+
public readonly runButton = '.kui--block-action-run'
24+
2225
public tipWithTitle(title: string) {
2326
return `${this.tip}[data-title="${title}"]`
2427
}
@@ -30,4 +33,8 @@ export default class Markdown {
3033
public tabWithTitle(title: string) {
3134
return `${this.tab}[data-title="${title}"]`
3235
}
36+
37+
public codeBlock(index: number) {
38+
return `${this._codeBlock}[data-code-index="${index}"]`
39+
}
3340
}

packages/test/src/api/Wizard.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default class Wizard {
2020
private readonly _description = '.pf-c-wizard__description'
2121
private readonly _navItem = '.pf-c-wizard__nav-item'
2222
private readonly _navItemTitle = '.pf-c-wizard__nav-link'
23+
public readonly isCurrentStep = 'pf-m-current'
2324
private readonly _navItemDescription = '.kui--wizard-nav-item-description'
2425
private readonly _navItemProgressStepper = '.kui--progress-stepper'
2526
private readonly _navItemProgressStep = '.kui--progress-step'
@@ -42,6 +43,10 @@ export default class Wizard {
4243
return `${this.wizard} ${this._navItem}:nth-child(${idx + 1})`
4344
}
4445

46+
public navItemSwitchToButton(idx: number) {
47+
return `${this.navItem(idx)} > button`
48+
}
49+
4550
public navItemTitle(idx: number) {
4651
return `${this.navItem(idx)} ${this._navItemTitle}`
4752
}

plugins/plugin-client-common/src/components/Content/Markdown/components/Wizard/Progress.tsx

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,93 +16,100 @@
1616

1717
import React from 'react'
1818
import { i18n } from '@kui-shell/core'
19-
20-
import { Graph, blocks, progress } from '../code/graph'
21-
22-
import { ProgressStepState, statusFromStatusVector } from '../../../ProgressStepper'
23-
import { subscribeToLinkUpdates, unsubscribeToLinkUpdates } from '../../../LinkStatus'
19+
import { ProgressVariant } from '@patternfly/react-core'
2420

2521
import { State as WizardState } from '.'
2622

27-
import { ProgressVariant } from '@patternfly/react-core'
23+
import {
24+
ReadinessHandler,
25+
emitCodeBlockReadiness,
26+
onGetCodeBlockReadiness,
27+
offGetCodeBlockReadiness
28+
} from '../../../../Views/Terminal/Block/CodeBlockEvents'
29+
30+
import { OrderedGraph, blocks, progress } from '../code/graph'
31+
import { ProgressStepState } from '../../../ProgressStepper'
2832

2933
const PatternFlyProgress = React.lazy(() => import('@patternfly/react-core').then(_ => ({ default: _.Progress })))
3034

3135
const strings = i18n('plugin-client-common', 'code')
3236

37+
type Status = ProgressStepState['status']
38+
3339
type Props = Pick<WizardState, 'choices'> & {
3440
/** The tasks to be accomplished */
35-
codeBlocks: Graph
36-
}
37-
38-
type Status = ProgressStepState['status']
41+
codeBlocks: OrderedGraph
3942

40-
interface State {
4143
/** The key is a codeBlockId */
4244
status: Record<string, Status>
4345
}
4446

47+
type State = ReturnType<typeof progress> & {
48+
nextCodeBlocks: string[]
49+
}
50+
4551
export default class Progress extends React.PureComponent<Props, State> {
52+
private readonly cleaners: (() => void)[] = []
53+
4654
public constructor(props: Props) {
4755
super(props)
4856

49-
this.state = {
50-
status: {}
51-
}
57+
this.state = Progress.getDerivedStateFromProps(props)
5258
}
5359

54-
private readonly _statusUpdateHandler = (statusVector: number[], codeBlockId: string) => {
55-
const status = statusFromStatusVector(statusVector, false)
60+
public static getDerivedStateFromProps(props: Props, state?: State) {
61+
const prog = progress(props.codeBlocks, props.status, props.choices)
62+
63+
const allCodeBlocks = blocks(props.codeBlocks)
64+
if (!state) {
65+
// initialize all blocks as not ready
66+
allCodeBlocks.forEach(_ => emitCodeBlockReadiness(_.id, false))
67+
}
68+
69+
const nextCodeBlocks = allCodeBlocks.filter(_ => prog.nextOrdinals.includes(_.order)).map(_ => _.id)
70+
71+
if (state) {
72+
const noLongerReady = state.nextCodeBlocks.filter(_ => !nextCodeBlocks.includes(_))
73+
noLongerReady.forEach(id => emitCodeBlockReadiness(id, false))
74+
}
75+
nextCodeBlocks.forEach(id => emitCodeBlockReadiness(id, true))
5676

57-
this.updateStatus(codeBlockId, status)
77+
return Object.assign({}, prog, { nextCodeBlocks })
5878
}
5979

60-
private updateStatus(codeBlockId: string, status: Status) {
61-
this.setState(curState => {
62-
curState.status[codeBlockId] = status
63-
return {
64-
status: Object.assign({}, curState.status)
65-
}
66-
})
80+
private readonly _onGetReadiness = (handler: ReadinessHandler, codeBlockId: string) => {
81+
const ready = this.state.nextCodeBlocks.includes(codeBlockId)
82+
handler(ready)
6783
}
6884

6985
public componentDidMount() {
70-
blocks(this.props.codeBlocks, 'all').forEach(_ => {
71-
subscribeToLinkUpdates(_.id, this._statusUpdateHandler)
86+
blocks(this.props.codeBlocks).forEach(({ id }) => {
87+
onGetCodeBlockReadiness(id, this._onGetReadiness)
88+
this.cleaners.push(() => offGetCodeBlockReadiness(id, this._onGetReadiness))
7289
})
7390
}
7491

7592
public componentWillUnmount() {
76-
blocks(this.props.codeBlocks, 'all').forEach(_ => {
77-
unsubscribeToLinkUpdates(_.id, this._statusUpdateHandler)
78-
})
79-
}
80-
81-
private get nSteps() {
82-
return progress(this.props.codeBlocks, undefined, this.props.choices).nTotal
83-
}
84-
85-
private counts() {
86-
return progress(this.props.codeBlocks, this.state.status, this.props.choices)
93+
this.cleaners.forEach(_ => _())
8794
}
8895

8996
public render() {
90-
const { nDone, nError } = this.counts()
97+
const { nDone, nError, nTotal } = this.state
9198

9299
const title = strings('Completed tasks')
93100
const label =
94101
nError > 0
95-
? strings(nError === 1 ? 'xOfyFailingz' : 'xOfyFailingsz', nDone, nError, this.nSteps)
96-
: strings('xOfy', nDone, this.nSteps)
102+
? strings(nError === 1 ? 'xOfyFailingz' : 'xOfyFailingsz', nDone, nError, nTotal)
103+
: strings('xOfy', nDone, nTotal)
97104

98-
const variant = nDone === this.nSteps ? ProgressVariant.success : nError > 0 ? ProgressVariant.danger : undefined
105+
const variant = nDone === nTotal ? ProgressVariant.success : nError > 0 ? ProgressVariant.danger : undefined
99106

100107
return (
101108
<PatternFlyProgress
102109
aria-label="wizard progress"
103110
className="kui--wizard-progress"
104111
min={0}
105-
max={this.nSteps}
112+
max={nTotal}
106113
value={nDone}
107114
title={title}
108115
label={label}

plugins/plugin-client-common/src/components/Content/Markdown/components/Wizard/index.tsx

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,56 @@ import { WizardProps } from './rehype-wizard'
2121
import Progress from './Progress'
2222
import CodeBlockProps from './CodeBlockProps'
2323
import { onTabSwitch, offTabSwitch } from '../tabbed'
24-
import { Graph, sequence, compile, blocks } from '../code/graph'
24+
import { Graph, OrderedGraph, blocks, compile, order, sequence } from '../code/graph'
2525

2626
import Card from '../../../../spi/Card'
2727
import { MiniProgressStepper } from '../../../MiniProgressStepper'
28+
import { ProgressStepState, statusFromStatusVector } from '../../../ProgressStepper'
29+
import { subscribeToLinkUpdates, unsubscribeToLinkUpdates } from '../../../LinkStatus'
2830

2931
import '../../../../../../web/scss/components/Wizard/PatternFly.scss'
3032

3133
const PatternFlyWizard = React.lazy(() => import('@patternfly/react-core').then(_ => ({ default: _.Wizard })))
3234

35+
type Status = ProgressStepState['status']
36+
3337
type Props = WizardProps & { uuid: string }
3438

3539
export interface State {
40+
codeBlocks: OrderedGraph
41+
42+
/** Map from codeBlock ID to execution status of that code block */
43+
status: Record<string, Status>
44+
3645
/** Map from tab group to currently selected tab member */
3746
choices: Record<string, string>
3847
}
3948

4049
export default class Wizard extends React.PureComponent<Props, State> {
4150
private readonly cleaners: (() => void)[] = []
4251

52+
private readonly _statusUpdateHandler = (statusVector: number[], codeBlockId: string) => {
53+
const status = statusFromStatusVector(statusVector, false)
54+
55+
this.updateStatus(codeBlockId, status)
56+
}
57+
4358
public constructor(props: Props) {
4459
super(props)
4560

4661
this.state = {
47-
choices: {}
62+
status: {},
63+
choices: {},
64+
codeBlocks: order(sequence(this.children.flatMap(_ => this.containedCodeBlocks(_)).filter(Boolean)))
4865
}
4966
}
5067

5168
public componentDidMount() {
69+
blocks(this.state.codeBlocks, 'all').forEach(_ => {
70+
subscribeToLinkUpdates(_.id, this._statusUpdateHandler)
71+
this.cleaners.push(() => unsubscribeToLinkUpdates(_.id, this._statusUpdateHandler))
72+
})
73+
5274
const switcher = (group: string, member: string) => {
5375
this.setState(curState => ({
5476
choices: Object.assign({}, curState.choices, { [group]: member })
@@ -63,10 +85,20 @@ export default class Wizard extends React.PureComponent<Props, State> {
6385
this.cleaners.forEach(_ => _())
6486
}
6587

88+
private updateStatus(codeBlockId: string, status: Status) {
89+
this.setState(curState => {
90+
curState.status[codeBlockId] = status
91+
return {
92+
status: Object.assign({}, curState.status)
93+
}
94+
})
95+
}
96+
6697
private wizardCodeBlockSteps(containedCodeBlocks: Graph) {
6798
return (
6899
containedCodeBlocks && (
69100
<MiniProgressStepper
101+
status={this.state.status}
70102
steps={blocks(containedCodeBlocks, this.state.choices).map(_ => ({
71103
codeBlockId: _.id,
72104
validate: _.validate,
@@ -111,14 +143,11 @@ export default class Wizard extends React.PureComponent<Props, State> {
111143

112144
/** Overall progress across all steps */
113145
private progress() {
114-
if (this.props['data-kui-wizard-progress'] === 'bar') {
115-
const allCodeBlocks = sequence(this.children.flatMap(_ => this.containedCodeBlocks(_)))
146+
if (this.props['data-kui-wizard-progress'] === 'bar' && blocks(this.state.codeBlocks).length > 0) {
116147
return (
117-
allCodeBlocks.sequence.length > 0 && (
118-
<div className="kui--markdown-major-paragraph">
119-
<Progress choices={this.state.choices} codeBlocks={allCodeBlocks} />
120-
</div>
121-
)
148+
<div className="kui--markdown-major-paragraph">
149+
<Progress status={this.state.status} choices={this.state.choices} codeBlocks={this.state.codeBlocks} />
150+
</div>
122151
)
123152
}
124153
}

0 commit comments

Comments
 (0)