/
ExecutionAndStagePicker.tsx
155 lines (134 loc) · 5.9 KB
/
ExecutionAndStagePicker.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import * as React from 'react';
import { keyBy, isEqual } from 'lodash';
import { Option } from 'react-select';
import { IExecution, IPipeline, IStage } from 'core/domain';
import { FormField, IStageForSpelPreview, ReactSelectInput, useData } from 'core/presentation';
import { ReactInjector } from 'core/reactShims';
import { timestamp, relativeTime } from 'core/utils';
import { ExecutionStatus } from 'core/pipeline/status/ExecutionStatus';
export interface IExecutionAndStagePickerProps {
pipeline: IPipeline;
pipelineStage: IStage;
onChange(chosenExecutionAndStage: IStageForSpelPreview): void;
}
export function ExecutionAndStagePicker(props: IExecutionAndStagePickerProps) {
const { pipeline, pipelineStage, onChange } = props;
const { executionService } = ReactInjector;
const [execution, setExecution] = React.useState<IExecution>(null);
const [stageId, setStageId] = React.useState<string>(null);
const [showStageSelector, setShowStageSelector] = React.useState(false);
const fetchExecutions = useData(
() => executionService.getExecutionsForConfigIds([pipeline.id], { limit: 100 }),
[],
[],
);
const executions = fetchExecutions.result;
const stages = execution && execution.stages;
// When executions load, select the first one.
React.useEffect(() => executions && setExecution(executions[0]), [executions]);
// When an execution is chosen or the current pipeline or pipeline stage changes,
// find the matching stage from the execution
React.useEffect(() => {
const exactStage = findExactStageFromExecution(pipeline, execution, pipelineStage);
setShowStageSelector(!!exactStage);
setStageId(exactStage || findCloseStageFromExecution(pipeline, execution, pipelineStage));
}, [pipeline, pipelineStage, execution]);
// When the chosen execution or stage changes, notify the parent
React.useEffect(() => {
onChange({ executionLabel: executionLabel(execution), executionId: execution && execution.id, stageId });
}, [execution, stageId]);
const stageOptions: Option[] = stages ? stages.map(x => ({ label: x.name, value: x.id })) : [];
const executionOptions: Array<Option<IExecution>> = executions ? executions.map(value => ({ value })) : [];
if (fetchExecutions.status === 'RESOLVED' && !executions.length) {
return (
<p>
This pipeline has never been executed. If you run this pipeline at least once, Spinnaker will show previews of
the variables on this screen.
</p>
);
}
return (
<>
<p>
Select a previous execution of this pipeline. Spinnaker will then evaluate each variable as if it had been part
of that execution, and generate a preview for you to debug against.
</p>
<FormField
label="Execution"
value={execution}
onChange={e => setExecution(e.target.value)}
input={inputProps => (
<ReactSelectInput<IExecution>
{...inputProps}
options={executionOptions as any}
clearable={false}
isLoading={fetchExecutions.status === 'PENDING'}
optionRenderer={option => (
<ExecutionStatus
execution={(option.value as any) as IExecution}
showingDetails={true}
standalone={true}
/>
)}
valueRenderer={value => <span>{executionLabel(value as any)}</span>}
/>
)}
/>
{!showStageSelector && (
<>
<p className="sp-margin-m-top">
Spinnaker could not find this exact same Evaluate Variables stage in the previous execution. Select a stage
from the previous execution to use as a stand-in.
</p>
<FormField
label="Stage"
value={stageId}
onChange={e => setStageId(e.target.value)}
input={inputProps => (
<ReactSelectInput {...inputProps} options={stageOptions} clearable={false} isLoading={!stages} />
)}
/>
</>
)}
</>
);
}
function executionLabel(execution: IExecution) {
if (!execution) {
return null;
}
const time = execution.buildTime || execution.startTime;
return `${timestamp(time)} (${relativeTime(time)})`;
}
function findExactStageFromExecution(pipeline: IPipeline, execution: IExecution, editStage: IStage): string {
if (!pipeline || !execution || !editStage) {
return null;
}
const pipelineStagesById = keyBy(pipeline.stages, 'refId');
const executionStagesById = keyBy(execution.stages, 'refId');
const exactMatch = execution.stages.find(s => s.refId === editStage.refId && s.type === editStage.type);
const pipelineRequisiteGraph = stageRequisiteStageGraph(editStage, pipelineStagesById);
const executionRequisiteGraph = stageRequisiteStageGraph(editStage, executionStagesById);
const exactGraphMatch = isEqual(pipelineRequisiteGraph, executionRequisiteGraph);
return exactMatch && exactGraphMatch ? exactMatch.id : null;
}
function findCloseStageFromExecution(pipeline: IPipeline, execution: IExecution, editStage: IStage): string {
if (!pipeline || !execution || !editStage) {
return null;
}
const pipelineStagesById = keyBy(pipeline.stages, 'refId');
const pipelineRequisiteGraph = stageRequisiteStageGraph(editStage, pipelineStagesById);
const upstreamStages = Object.keys(pipelineRequisiteGraph).map(id => pipelineStagesById[id]);
return upstreamStages.map(stage => findExactStageFromExecution(pipeline, execution, stage)).find(x => !!x);
}
function stageRequisiteStageGraph(stage: IStage, allStages: { [key: string]: IStage }): { [key: string]: any } {
return stage.requisiteStageRefIds
.filter(ref => /^[0-9]+$/.exec(ref.toString()))
.reduce((acc, requisiteStageId) => {
const requisiteStage = allStages[requisiteStageId];
if (!requisiteStage) {
return acc;
}
return { ...acc, [requisiteStageId]: stageRequisiteStageGraph(requisiteStage, allStages) };
}, {});
}