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

Commit d78cc7c

Browse files
committed
fix(plugins/plugin-bash-like): multi-part streaming responses can disappear and reappear
1 parent 374fa5b commit d78cc7c

File tree

6 files changed

+48
-22
lines changed

6 files changed

+48
-22
lines changed

plugins/plugin-bash-like/src/pty/client.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,13 +631,16 @@ async function initOnMessage(
631631
// !! DO NOT CALL xtermContainer.remove() directly !!
632632
execOptions.stdout(null)
633633

634-
// respond to the REPL
635-
respondToRepl({
634+
const response: XtermResponse = {
636635
apiVersion: 'kui-shell/v1',
637636
kind: 'XtermResponse',
638637
rows: copy(terminal),
639638
code: 0 // to be over-written in the case of an error response
640-
})
639+
}
640+
execOptions.stdout(response)
641+
642+
// respond to the REPL
643+
respondToRepl(response)
641644
})
642645
}
643646

plugins/plugin-client-common/src/components/Content/Scalar/XtermDom.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,7 @@ export default class XtermDom extends React.PureComponent<Props> {
5050
return this.props.response.rows.length === 0 ? (
5151
<React.Fragment />
5252
) : (
53-
<div
54-
className="kui--xterm-output padding-content scrollable scrollable-auto page-content"
55-
style={{ display: 'flex', flex: 1 }}
56-
>
53+
<div className="kui--xterm-output padding-content scrollable scrollable-auto page-content">
5754
<div style={{ display: 'flex', flex: 1 }}>
5855
<div className="xterm-container xterm-terminated">
5956
<div className="xterm-rows">

plugins/plugin-client-common/src/components/Views/Terminal/Block/Output.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ export default class Output extends React.PureComponent<Props, State> {
132132
Events.eventChannelUnsafe.emit(`/command/stdout/done/${tabUUID}/${execUUID}`)
133133
}
134134

135-
// part === null: the controller wants to clear any prior output
135+
// part === null: the controller wants to clear prior output
136136
if (part === null) {
137-
this.streamingOutput = []
137+
// remove last output
138+
this.streamingOutput.pop()
139+
this.setState(curState => ({
140+
nStreamingOutputs: curState.nStreamingOutputs - 1
141+
}))
138142
done()
139-
return {
140-
// remove all output
141-
nStreamingOutputs: 0
142-
}
143143
} else {
144144
if (isOutputRedirected(this.props.model)) {
145145
// if we were asked to redirect to a file, then we can
@@ -216,8 +216,24 @@ export default class Output extends React.PureComponent<Props, State> {
216216

217217
private stream() {
218218
if (this.hasStreamingOutput()) {
219-
if (this.streamingOutput.every(_ => typeof _ === 'string')) {
220-
const combined = this.streamingOutput.join('')
219+
// we may have duplicates in streamingOutput and response
220+
// see https://github.com/kubernetes-sigs/kui/pull/8354
221+
const response = isWithCompleteEvent(this.props.model) ? this.props.model.response : undefined
222+
const streamingOutput = !response
223+
? this.streamingOutput
224+
: this.streamingOutput.filter(_ => {
225+
if (isMixedResponse(response)) {
226+
return !response.find(_2 => _ === _2)
227+
} else {
228+
return _ !== response
229+
}
230+
})
231+
if (streamingOutput.length < this.streamingOutput.length) {
232+
this.streamingOutput = streamingOutput
233+
}
234+
235+
if (streamingOutput.every(_ => typeof _ === 'string')) {
236+
const combined = streamingOutput.join('')
221237
return (
222238
<div className="repl-result-like result-vertical" data-stream>
223239
<React.Suspense fallback={<div />}>
@@ -229,7 +245,7 @@ export default class Output extends React.PureComponent<Props, State> {
229245

230246
return (
231247
<div className="repl-result-like result-vertical" data-stream>
232-
{this.streamingOutput.map((part, idx) => (
248+
{streamingOutput.map((part, idx) => (
233249
<React.Suspense fallback={<div />} key={idx}>
234250
<Scalar
235251
tab={this.props.tab}

plugins/plugin-client-common/src/components/Views/Terminal/Block/StreamingConsumer.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ export default abstract class StreamingConsumer<
5959
Events.eventChannelUnsafe.emit(`/command/stdout/done/${tab.uuid}/${execUUID}`)
6060
}
6161

62-
// part === null: the controller wants to clear any prior output
62+
// part === null: the controller wants to clear prior output
6363
if (part === null) {
64-
this._streamingOutput = []
64+
// remove last output
65+
this._streamingOutput.pop()
66+
this.setState(curState => ({
67+
nStreamingOutputs: curState.nStreamingOutputs - 1
68+
}))
6569
done()
66-
return {
67-
// remove all output
68-
nStreamingOutputs: 0
69-
}
7070
} else {
7171
this._streamingOutput.push(Buffer.isBuffer(part) ? part.toString() : part)
7272

plugins/plugin-client-common/web/scss/components/Terminal/Commentary.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ body[kui-theme-style='light'] {
286286
flex-direction: column; /* in case of multi-output, e.g. from semicolonInvoke */
287287
font-size: 0.875rem;
288288
line-height: initial; /* PatternFly intercepts this for pf-c-content */
289+
290+
@include Markdown {
291+
flex: initial; /* repl-result has flex-direction: column */
292+
}
289293
}
290294
}
291295

plugins/plugin-client-common/web/scss/components/Terminal/_mixins.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,3 +557,9 @@ $action-hover-delay: 210ms;
557557
@content;
558558
}
559559
}
560+
561+
@mixin Markdown {
562+
.page-content {
563+
@content;
564+
}
565+
}

0 commit comments

Comments
 (0)