Skip to content
9 changes: 9 additions & 0 deletions src/main/frontend/common/RestClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export interface RunStatus {
complete: boolean;
}

export interface InputStep {
message: string;
cancel: string;
id: string;
ok: string;
parameters: boolean;
}

/**
* StageInfo is the input, in the form of an Array<StageInfo> of the top-level stages of a pipeline
*/
Expand All @@ -17,6 +25,7 @@ export interface StepInfo {
title: string;
state: Result;
completePercent: number;
inputStep?: InputStep;
id: string;
type: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the values of type? Could it be set to input instead of a new field?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its STEP, it could be but I need a number of fields anyway

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I missed deleting this comment after a further look through the rest of the code

stageId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,12 @@ export interface ConsoleLineProps {
heightCallback: (height: number) => void;
}

declare global {
interface Window {
Behaviour: any;
}
}

// Console output line
export const ConsoleLine = memo(function ConsoleLine(props: ConsoleLineProps) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const height = ref.current ? ref.current.getBoundingClientRect().height : 0;
props.heightCallback(height);

// apply any behaviour selectors to the new content, e.g. for input step
window.Behaviour.applySubtree(
document.getElementById(`${props.stepId}-${props.lineNumber}`),
);
}, []);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,40 +41,61 @@ export default function ConsoleLogCard(props: ConsoleLogCardProps) {
props.onStepToggle(props.step.id);
};

const showMoreLogs = () => {
let startByte = props.stepBuffer.startByte - LOG_FETCH_SIZE;
if (startByte < 0) startByte = 0;
props.onMoreConsoleClick(props.step.id, startByte);
};
const messages = useMessages();

const getTruncatedLogWarning = () => {
if (props.stepBuffer.lines && props.stepBuffer.startByte > 0) {
return (
<button
onClick={showMoreLogs}
className={
"pgv-show-more-logs jenkins-button jenkins-!-warning-color"
const inputStep = props.step.inputStep;
if (inputStep && !inputStep.parameters) {
function handler(id: string, action: string) {
fetch(`../input/${id}/${action}`, {
method: "POST",
headers: window.crumb.wrap({}),
})
.then((res) => {
if (!res.ok) {
console.error(res);
}
>
There’s more to see - {prettySizeString(props.stepBuffer.startByte)}{" "}
of logs hidden
</button>
);
return true;
})
.catch((err) => {
console.error(err);
});
}
return undefined;
};

const prettySizeString = (size: number) => {
const kib = 1024;
const mib = 1024 * 1024;
const gib = 1024 * 1024 * 1024;
if (size < kib) return `${size}B`;
if (size < mib) return `${(size / kib).toFixed(2)}KiB`;
if (size < gib) return `${(size / mib).toFixed(2)}MiB`;
return `${(size / gib).toFixed(2)}GiB`;
};
const ok = () => {
return handler(inputStep.id, "proceedEmpty");
};

const messages = useMessages();
const abort = () => {
return handler(inputStep.id, "abort");
};

return (
<div className="pgv-input-step">
<div className="pgv-step-detail-header__content">
<StatusIcon
status={props.step.state}
percentage={props.step.completePercent}
/>
<span>{inputStep.message}</span>
</div>
<div
className={
"jenkins-buttons-row jenkins-buttons-row--equal-width pgv-input-step__controls"
}
>
<button
onClick={ok}
className={"jenkins-button jenkins-button--primary"}
>
{inputStep.ok}
</button>
<button onClick={abort} className={"jenkins-button"}>
{inputStep.cancel}
</button>
</div>
</div>
);
}

return (
<div className={"pgv-step-detail-group"} key={`step-card-${props.step.id}`}>
Expand Down Expand Up @@ -162,22 +183,77 @@ export default function ConsoleLogCard(props: ConsoleLogCardProps) {
</div>

{props.isExpanded && (
<div style={{ paddingTop: "0.5rem" }}>
{getTruncatedLogWarning()}
<Suspense>
<ConsoleLogStream
logBuffer={props.stepBuffer}
onMoreConsoleClick={props.onMoreConsoleClick}
step={props.step}
maxHeightScale={0.65}
/>
</Suspense>
</div>
<ConsoleLogBody
step={props.step}
stepBuffer={props.stepBuffer}
onMoreConsoleClick={props.onMoreConsoleClick}
isExpanded={false}
onStepToggle={props.onStepToggle}
/>
)}
</div>
);
}

declare global {
interface Window {
crumb: Crumb;
}

interface Crumb {
wrap: (headers: Record<string, string>) => Record<string, string>;
}
}

function ConsoleLogBody(props: ConsoleLogCardProps) {
const prettySizeString = (size: number) => {
const kib = 1024;
const mib = 1024 * 1024;
const gib = 1024 * 1024 * 1024;
if (size < kib) return `${size}B`;
if (size < mib) return `${(size / kib).toFixed(2)}KiB`;
if (size < gib) return `${(size / mib).toFixed(2)}MiB`;
return `${(size / gib).toFixed(2)}GiB`;
};

const showMoreLogs = () => {
let startByte = props.stepBuffer.startByte - LOG_FETCH_SIZE;
if (startByte < 0) startByte = 0;
props.onMoreConsoleClick(props.step.id, startByte);
};

const getTruncatedLogWarning = () => {
if (props.stepBuffer.lines && props.stepBuffer.startByte > 0) {
return (
<button
onClick={showMoreLogs}
className={
"pgv-show-more-logs jenkins-button jenkins-!-warning-color"
}
>
There’s more to see - {prettySizeString(props.stepBuffer.startByte)}{" "}
of logs hidden
</button>
);
}
return undefined;
};

return (
<div style={{ paddingTop: "0.5rem" }}>
{getTruncatedLogWarning()}
<Suspense>
<ConsoleLogStream
logBuffer={props.stepBuffer}
onMoreConsoleClick={props.onMoreConsoleClick}
step={props.step}
maxHeightScale={0.65}
/>
</Suspense>
</div>
);
}

export type ConsoleLogCardProps = {
step: StepInfo;
stepBuffer: StepLogBufferInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,6 @@ import {
StepLogBufferInfo,
} from "./PipelineConsoleModel.tsx";

beforeAll(() => {
window.Behaviour = {
applySubtree: vi.fn(),
};
});

afterAll(() => {
delete window.Behaviour;
});

const TestComponent = (props: ConsoleLogStreamProps) => {
return (
<div id="test-parent">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,38 @@ a.console-line-number {
}
}

.pgv-input-step {
position: relative;
z-index: 0;
padding: 0.875rem;
margin-bottom: -0.375rem;
margin-left: -0.375rem;
margin-right: -0.375rem;
// TODO - var fallback can removed after baseline is moved >= 2.496
border-top: var(
--jenkins-border,
2px solid color-mix(in srgb, var(--text-color-secondary) 10%, transparent)
);
background-color: color-mix(in srgb, var(--accent-color) 2.5%, transparent);
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;

&:first-of-type {
margin-top: -0.375rem;
border-top: none;
border-radius: 0.375rem;
}

.jenkins-button {
min-width: 7.5rem;
}

&__controls {
padding-left: 2.275rem;
margin-top: 0.75rem;
}
}

div.console-output-line {
position: relative;
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@
import io.jenkins.plugins.pipelinegraphview.utils.FlowNodeWrapper;
import io.jenkins.plugins.pipelinegraphview.utils.NodeRunStatus;
import io.jenkins.plugins.pipelinegraphview.utils.PipelineNodeUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graph.AtomNode;
import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
import org.jenkinsci.plugins.workflow.support.steps.input.InputStep;
import org.jenkinsci.plugins.workflow.support.steps.input.InputStepExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -167,6 +174,7 @@ private static class GraphBuilder {
// FlowNodeWrapper rootStage = null;

private final Logger logger = LoggerFactory.getLogger(GraphBuilder.class);
private final InputAction inputAction;
private boolean isDebugEnabled = logger.isDebugEnabled();

/*
Expand All @@ -181,6 +189,7 @@ public GraphBuilder(
this.nodeMap = nodeMap;
this.relationships = relationships;
this.run = run;
this.inputAction = run.getAction(InputAction.class);
this.execution = execution;
buildGraph();
}
Expand Down Expand Up @@ -491,7 +500,24 @@ private void assignParent(@NonNull FlowNodeWrapper wrappedNode, @CheckForNull Fl
timing = relationship.getTimingInfo(this.run);
status = relationship.getStatus(this.run);
}
return new FlowNodeWrapper(node, status, timing, this.run);

InputStep inputStep = null;
if (node instanceof AtomNode atomNode
&& PipelineNodeUtil.isPausedForInputStep((StepAtomNode) atomNode, inputAction)) {
try {
for (InputStepExecution execution : inputAction.getExecutions()) {
FlowNode theNode = execution.getContext().get(FlowNode.class);
if (theNode != null && theNode.equals(atomNode)) {
inputStep = execution.getInput();
break;
}
}
} catch (IOException | InterruptedException | TimeoutException e) {
logger.error("Error getting FlowNode from execution context: {}", e.getMessage(), e);
}
}

return new FlowNodeWrapper(node, status, timing, inputStep, this.run);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.jenkins.plugins.pipelinegraphview.utils;

public record PipelineInputStep(String message, String cancel, String id, String ok, boolean parameters) {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jackson with our current config can't serialise the parameters object (if not more) so just map what we need

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import io.jenkins.plugins.pipelinegraphview.analysis.TimingInfo;

public class PipelineStep extends AbstractPipelineNode {
private String stageId;
private final String stageId;
private final PipelineInputStep inputStep;

public PipelineStep(
String id,
Expand All @@ -12,12 +13,18 @@ public PipelineStep(
String type,
String title,
String stageId,
PipelineInputStep inputStep,
TimingInfo timingInfo) {
super(id, name, state, type, title, timingInfo);
this.stageId = stageId;
this.inputStep = inputStep;
}

public String getStageId() {
return stageId;
}

public PipelineInputStep getInputStep() {
return inputStep;
}
}
Loading