Skip to content

Commit

Permalink
[FIX JENKINS-40242] preserve unknown sections (#12)
Browse files Browse the repository at this point in the history
* Import/export handles unknown sections and steps
* Handle unknown steps using a pipeline script editor
  • Loading branch information
kzantow committed Jan 4, 2017
1 parent a07dcdc commit a0b8793
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 22 deletions.
18 changes: 13 additions & 5 deletions src/main/js/components/editor/EditorMain.jsx
Expand Up @@ -120,17 +120,25 @@ export class EditorMain extends Component<DefaultProps, Props, State> {
this.setState({selectedStep});
}

stepDataChanged(newValue:any) {

const {selectedStep} = this.state;
stepDataChanged(newStep:any) {
const {selectedStage,selectedStep} = this.state;

if (!selectedStep) {
console.log("unable to set new step data, no currently selected step");
return;
}
selectedStep.data = newValue;

const parentStep = pipelineStore.findParentStep(selectedStep);
const stepArray = (parentStep && parentStep.children) || selectedStage.steps;
let idx = 0;
for (; idx < stepArray.length; idx++) {
if (stepArray[idx].id === selectedStep.id) {
break;
}
}
stepArray[idx] = newStep;
this.setState({
selectedStep: selectedStep
selectedStep: newStep
});
}

Expand Down
15 changes: 12 additions & 3 deletions src/main/js/components/editor/EditorStepDetails.jsx
Expand Up @@ -3,7 +3,8 @@
import React, {Component, PropTypes} from 'react';
import pipelineStepListStore from '../../services/PipelineStepListStore';
import type {StepInfo} from './common';
import GenericEditor from './steps/GenericStepEditor';
import GenericStepEditor from './steps/GenericStepEditor';
import UnknownStepEditor from './steps/UnknownStepEditor';

const allStepEditors = [
require('./steps/ShellScriptStepEditor').default,
Expand Down Expand Up @@ -51,7 +52,7 @@ export class EditorStepDetails extends Component {
commitValue(step) {
const {onDataChange} = this.props;
if (onDataChange) {
onDataChange(step.data);
onDataChange(step);
}
}

Expand All @@ -70,7 +71,15 @@ export class EditorStepDetails extends Component {
if (editor) {
return editor;
}
return GenericEditor;
if (!this.state.stepMetadata) {
return null;
}

const foundMeta = this.state.stepMetadata.filter(md => md.functionName === step.name);
if (foundMeta.length === 0) {
return UnknownStepEditor;
}
return GenericStepEditor;
}

render() {
Expand Down
56 changes: 56 additions & 0 deletions src/main/js/components/editor/steps/UnknownStepEditor.jsx
@@ -0,0 +1,56 @@
// @flow

import React, { Component, PropTypes } from 'react';
import debounce from 'lodash.debounce';
import { TextInput } from '@jenkins-cd/design-language';
import { convertPipelineStepsToJson, convertJsonStepsToPipeline, convertStepsToJson, convertStepFromJson } from '../../../services/PipelineSyntaxConverter';
import type { PipelineStep } from '../../../services/PipelineSyntaxConverter';

type Props = {
onChange: Function,
step: any,
}

type State = {
stepScript: Array<any>,
};

type DefaultProps = typeof UnknownStepEditorPanel.defaultProps;

export default class UnknownStepEditorPanel extends Component<DefaultProps, Props, State> {
props:Props;
state:State;

constructor(props:Props) {
super(props);
this.state = { stepScript: null };
}

componentWillMount() {
convertJsonStepsToPipeline(convertStepsToJson([this.props.step]), stepScript => {
this.setState({stepScript: stepScript});
});
}

updateStepData = debounce(stepScript => {
this.setState({stepScript: stepScript});
convertPipelineStepsToJson(stepScript, (stepJson, errors) => {
const newStep = convertStepFromJson(stepJson[0]);
newStep.id = this.props.step.id;
this.props.onChange(newStep);
});
}, 300);

render() {
const { step } = this.props;
const { stepScript } = this.state;

if (!step || !stepScript) {
return null;
}

return (<textarea className="editor-step-detail-script"
defaultValue={stepScript}
onChange={(e) => this.updateStepData(e.target.value)} />);
}
}
11 changes: 10 additions & 1 deletion src/main/js/services/PipelineStore.js
Expand Up @@ -6,7 +6,7 @@
export type StageInfo = {
name: string,
id: number,
children: StageInfo[],
children: Array<StageInfo|UnknownSection>,
steps: StepInfo[],
};

Expand All @@ -26,6 +26,15 @@ export type PipelineInfo = StageInfo & {
agent: StepInfo,
};

export class UnknownSection {
prop: string;
json: any;
constructor(prop: string, json: any) {
this.prop = prop;
this.json = json;
}
}

function _copy<T>(obj: T): ?T {
if (!obj) {
return null;
Expand Down
91 changes: 78 additions & 13 deletions src/main/js/services/PipelineSyntaxConverter.js
@@ -1,6 +1,7 @@
// @flow

import { Fetch, UrlConfig } from '@jenkins-cd/blueocean-core-js';
import { UnknownSection } from './PipelineStore';
import type { PipelineInfo, StageInfo, StepInfo } from './PipelineStore';
import pipelineStepListStore from './PipelineStepListStore';

Expand Down Expand Up @@ -38,6 +39,8 @@ export type PipelineStage = {
steps?: PipelineStep[],
};

const idgen = { id: 0, next() { return --this.id; } };

function singleValue(v: any) {
if (Array.isArray(v)) {
return v[0];
Expand All @@ -47,8 +50,25 @@ function singleValue(v: any) {
};
}

function captureUnknownSections(pipeline: any, internal: any, ...knownSections: string[]) {
for (const prop of Object.keys(pipeline)) {
if (knownSections.indexOf(prop) >= 0) {
continue;
}
internal[prop] = new UnknownSection(prop, pipeline[prop]);
}
}

function restoreUnknownSections(internal: any, out: any) {
for (const prop of Object.keys(internal)) {
const val = internal[prop];
if (val instanceof UnknownSection) {
out[val.prop] = val.json;
}
}
}

export function convertJsonToInternalModel(json: PipelineJsonContainer): PipelineInfo {
let idgen = { id: 0, next() { return --this.id; } };
const pipeline = json.pipeline;
const out: PipelineInfo = {
id: idgen.next(),
Expand All @@ -71,6 +91,9 @@ export function convertJsonToInternalModel(json: PipelineJsonContainer): Pipelin
throw new Error('Pipeline must define stages');
}

// capture unknown sections
captureUnknownSections(pipeline, out, 'agent', 'stages');

for (let i = 0; i < pipeline.stages.length; i++) {
const topStage = pipeline.stages[i];

Expand Down Expand Up @@ -102,9 +125,11 @@ export function convertJsonToInternalModel(json: PipelineJsonContainer): Pipelin
topStageInfo.children.push(stage);
}

captureUnknownSections(b, stage, 'name', 'steps');

for (let stepIndex = 0; stepIndex < b.steps.length; stepIndex++) {
const s = b.steps[stepIndex];
const step = readStepFromJson(s, idgen);
const step = convertStepFromJson(s);
stage.steps.push(step);
}
}
Expand All @@ -113,13 +138,20 @@ export function convertJsonToInternalModel(json: PipelineJsonContainer): Pipelin
return out;
}

function readStepFromJson(s: PipelineStep, idgen: any) {
export function convertStepFromJson(s: PipelineStep) {
// this will already have been called and cached:
let stepMeta = [];
pipelineStepListStore.getStepListing(steps => {
stepMeta = steps;
});
const meta = stepMeta.filter(md => md.functionName === s.name)[0];
const meta = stepMeta.filter(md => md.functionName === s.name)[0]

// handle unknown steps
|| {
isBlockContainer: false,
displayName: s.name,
};

const step = {
name: s.name,
label: meta.displayName,
Expand Down Expand Up @@ -150,7 +182,7 @@ function readStepFromJson(s: PipelineStep, idgen: any) {
}
if (s.children && s.children.length > 0) {
for (const c of s.children) {
const child = readStepFromJson(c, idgen);
const child = convertStepFromJson(c);
step.children.push(child);
}
}
Expand Down Expand Up @@ -183,22 +215,22 @@ function _convertStepArguments(step: StepInfo): PipelineNamedValueDescriptor[] {
return out;
}

function _convertStepsToJson(steps: StepInfo[]): PipelineStep[] {
export function convertStepsToJson(steps: StepInfo[]): PipelineStep[] {
const out: PipelineStep[] = [];
for (const step of steps) {
const s: PipelineStep = {
name: step.name,
arguments: _convertStepArguments(step),
};
if (step.children && step.children.length > 0) {
s.children = _convertStepsToJson(step.children);
s.children = convertStepsToJson(step.children);
}
out.push(s);
}
return out;
}

function _convertStageToJson(stage: StageInfo): PipelineStage {
export function convertStageToJson(stage: StageInfo): PipelineStage {
const out: PipelineStage = {
name: stage.name,
};
Expand All @@ -209,19 +241,25 @@ function _convertStageToJson(stage: StageInfo): PipelineStage {

// TODO Currently, sub-stages are not supported, this should be recursive
for (const child of stage.children) {
out.branches.push({
const outStage: PipelineStage = {
name: child.name,
steps: _convertStepsToJson(child.steps),
});
steps: convertStepsToJson(child.steps),
};

restoreUnknownSections(child, outStage);

out.branches.push(outStage);
}
} else {
// single, add a 'default' branch
out.branches = [
{
name: 'default',
steps: _convertStepsToJson(stage.steps),
steps: convertStepsToJson(stage.steps),
}
];

restoreUnknownSections(stage, out.branches[0]);
}

return out;
Expand All @@ -235,8 +273,11 @@ export function convertInternalModelToJson(pipeline: PipelineInfo): PipelineJson
},
};
const outPipeline = out.pipeline;

restoreUnknownSections(pipeline, outPipeline);

for (const stage of pipeline.children) {
const s = _convertStageToJson(stage);
const s = convertStageToJson(stage);
outPipeline.stages.push(s);
}
return out;
Expand Down Expand Up @@ -302,3 +343,27 @@ export function convertJsonToPipeline(json: string, handler: Function) {
});
});
}

export function convertPipelineStepsToJson(pipeline: string, handler: Function) {
pipelineStepListStore.getStepListing(steps => {
fetch(`${UrlConfig.getJenkinsRootURL()}/pipeline-model-converter/stepsToJson`,
'jenkinsfile=' + encodeURIComponent(pipeline), data => {
if (data.errors) {
console.log(data);
}
handler(data.json, data.errors);
});
});
}

export function convertJsonStepsToPipeline(step: PipelineStep, handler: Function) {
pipelineStepListStore.getStepListing(steps => {
fetch(`${UrlConfig.getJenkinsRootURL()}/pipeline-model-converter/stepsToJenkinsfile`,
'json=' + encodeURIComponent(JSON.stringify(step)), data => {
if (data.errors) {
console.log(data);
}
handler(data.jenkinsfile, data.errors);
});
});
}

0 comments on commit a0b8793

Please sign in to comment.