Skip to content

Commit

Permalink
#416 add 'when' to diagram
Browse files Browse the repository at this point in the history
Signed-off-by: Yevhen Vydolob <[email protected]>
  • Loading branch information
evidolob committed Oct 20, 2020
1 parent 4a607c6 commit a42acc5
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 29 deletions.
12 changes: 12 additions & 0 deletions preview-src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ function getStyle(style: CyTheme): cytoscape.Stylesheet[] {
'color': style.labelColor,
},
},
{
selector: 'node[type = "When"]',
style: {
'shape': 'diamond',
'width': 20,
'height': 20,
'text-wrap': 'wrap',
'text-valign': 'center',
'text-halign': 'left',
'color': style.labelColor,
},
},
{
selector: 'node[?final]',
style: {
Expand Down
2 changes: 2 additions & 0 deletions src/model/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export enum TknElementType {
PIPELINE_TASK_PARAMS,
PIPELINE_TASK_WORKSPACE,
PIPELINE_TASK_WORKSPACES,
PIPELINE_TASK_WHEN,
PIPELINE_TASK_WHEN_VALUES,
}

export function insideElement(element: TknElement, pos: number): boolean {
Expand Down
57 changes: 55 additions & 2 deletions src/model/pipeline/pipeline-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,43 @@ export class PipelineDeclaredResource extends NodeTknElement {
}
}

export class WhenTask extends NodeTknElement {
type = TknElementType.PIPELINE_TASK_WHEN;
private _input: TknStringElement;
private _operator: TknStringElement;
private _values: TknArray<TknStringElement>

get input(): TknStringElement {
if (!this._input) {
this._input = new TknStringElement(this, findNodeByKey('input', this.node as YamlMap))
}
return this._input;
}

get operator(): TknStringElement {
if (!this._operator) {
this._operator = new TknStringElement(this, findNodeByKey('operator', this.node as YamlMap))
}
return this._operator;
}

get values(): TknArray<TknStringElement> {
if (!this._values) {
const valuesNode = findNodeByKey<YamlSequence>('values', this.node as YamlMap)
if (valuesNode) {
this._values = new TknArray(TknElementType.PIPELINE_TASK_WHEN_VALUES, TknStringElement, this, valuesNode);
}
}

return this._values;
}

collectChildren(): TknElement[] {
return [this.input, this.operator, this.values];
}

}

export class PipelineTask extends NodeTknElement {

type = TknElementType.PIPELINE_TASK;
Expand All @@ -167,6 +204,7 @@ export class PipelineTask extends NodeTknElement {
private _params: TknArray<TknParam>;
private _workspaces: TknArray<WorkspacePipelineTaskBinding>;
private _timeout: TknStringElement;
private _when: TknArray<WhenTask>;

get name(): TknStringElement {
if (!this._name) {
Expand All @@ -177,7 +215,11 @@ export class PipelineTask extends NodeTknElement {

get taskRef(): PipelineTaskRef {
if (!this._taskRef) {
this._taskRef = new PipelineTaskRef(this, pipelineYaml.getTaskRef(this.node as YamlMap));

const taskRefNode = pipelineYaml.getTaskRef(this.node as YamlMap);
if (taskRefNode) {
this._taskRef = new PipelineTaskRef(this, taskRefNode);
}
}
return this._taskRef;
}
Expand Down Expand Up @@ -259,8 +301,19 @@ export class PipelineTask extends NodeTknElement {
return this._timeout;
}

get when(): TknArray<WhenTask> {
if (!this._when) {
const whenNode = findNodeByKey<YamlSequence>('when', this.node as YamlMap)
if (whenNode) {
this._when = new TknArray(TknElementType.PIPELINE_TASK_WHEN, WhenTask, this, whenNode);
}
}

return this._when;
}

collectChildren(): TknElement[] {
return [this.name, this.taskRef, this.conditions, this.retries, this.runAfter, this.resources, this.params, this.workspaces, this.timeout];
return [this.name, this.taskRef, this.conditions, this.retries, this.runAfter, this.resources, this.params, this.workspaces, this.timeout, this.when];
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/pipeline/pipeline-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ export async function askToSelectPipeline(pipeDocs: YamlDocument[], type: Tekton
function convertTasksToNode(tasks: PipelineRunTask[], includePositions = true): NodeOrEdge[] {
const result: NodeOrEdge[] = [];
const tasksMap = new Map<string, PipelineRunTask>();
tasks.forEach((task: DeclaredTask) => tasksMap.set(task.name, task));
tasks.forEach((task: DeclaredTask) => tasksMap.set( task.id, task));

for (const task of tasks) {
result.push({ data: { id: task.name, label: getLabel(task), type: task.kind, taskRef: task.taskRef, state: task.state, yamlPosition: includePositions ? task.position : undefined, final: task.final } as NodeData });
result.push({ data: { id: task.id, label: getLabel(task), type: task.kind, taskRef: task.taskRef, state: task.state, yamlPosition: includePositions ? task.position : undefined, final: task.final } as NodeData });
for (const after of task.runAfter ?? []) {
if (tasksMap.has(after)) {
result.push({ data: { source: after, target: task.name, id: `${after}-${task.name}`, state: tasksMap.get(after).state } as EdgeData });
result.push({ data: { source: after, target: task.id, id: `${after}-${ task.id}`, state: tasksMap.get(after).state } as EdgeData });
}
}
}
Expand All @@ -121,6 +121,9 @@ function convertTasksToNode(tasks: PipelineRunTask[], includePositions = true):

function getLabel(task: PipelineRunTask): string {
let label = task.name;
if (!label){
return '';
}
if (task.kind === 'Condition') {
return label;
}
Expand Down
131 changes: 110 additions & 21 deletions src/yaml-support/tkn-yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ export interface DeclaredResource {
}

export interface DeclaredTask {
id: string;
name: string;
taskRef: string;
runAfter: string[];
kind: 'Task' | 'ClusterTask' | 'Condition' | string;
kind: 'Task' | 'ClusterTask' | 'Condition' | 'When' | string;
position?: number;
final?: boolean;
}
Expand Down Expand Up @@ -333,27 +334,24 @@ export const pipelineRunYaml = new PipelineRunYaml();

function collectTasks(specMap: YamlMap): DeclaredTask[] {
const result: DeclaredTask[] = [];
const lastTasks: string[] = [];
if (specMap) {
const tasksSeq = getTasksSeq(specMap);
if (tasksSeq) {
for (const taskNode of tasksSeq.items) {
if (taskNode.kind === 'MAPPING') {
const decTask = toDeclaredTask(taskNode as YamlMap);
decTask.runAfter = getRunAfter(taskNode as YamlMap);
if (decTask.runAfter.length === 0){
lastTasks.push(decTask.name);
}
decTask.runAfter = collectRunAfter(taskNode as YamlMap, decTask.name);
collectConditions(taskNode as YamlMap, result);
collectWhen(taskNode as YamlMap, result, decTask.name);
result.push(decTask);
}
}
}


// collect finally tasks
const finallyTasks = findNodeByKey<YamlSequence>('finally', specMap);
if (finallyTasks) {
const lastTasks = getLastTasks(result);
for (const finalTask of finallyTasks.items) {
if (finalTask.kind === 'MAPPING') {
const fTask = toDeclaredTask(finalTask as YamlMap);
Expand All @@ -368,36 +366,84 @@ function collectTasks(specMap: YamlMap): DeclaredTask[] {
return result;
}

function getLastTasks(tasks: DeclaredTask[]): string[]{
const afterTasks = new Set<string>();
for (const task of tasks) {
task.runAfter.forEach(v => afterTasks.add(v));
}

const result: string[] = [];

for (const task of tasks) {
if (!afterTasks.has(task.id)) {
result.push(task.id);
}
}

return result;
}

function toDeclaredTask(taskNode: YamlMap): DeclaredTask {
const decTask = {} as DeclaredTask;
const nameValue = findNodeByKey<YamlNode>('name', taskNode);
if (nameValue) {
decTask.name = nameValue.raw;
const name = nameValue.raw?.trim();
decTask.name = name;
decTask.id = name;
decTask.position = nameValue.startPosition;
}

const taskRef = pipelineYaml.getTaskRef(taskNode);
if (taskRef) {
const taskRefName = findNodeByKey<YamlNode>('name', taskRef);
decTask.taskRef = taskRefName.raw;
decTask.taskRef = taskRefName.raw.trim();
const kindName = findNodeByKey<YamlNode>('kind', taskRef);
if (kindName) {
decTask.kind = kindName.raw;
} else {
decTask.kind = 'Task';
}
} else {
const taskSpec = findNodeByKey('taskSpec', taskNode);
if (taskSpec) {
decTask.kind = 'Task'
}

}
return decTask;
}

function collectWhen(taskNode: YamlMap, tasks: DeclaredTask[], taskName: string): void {
const whens = findNodeByKey<YamlSequence>('when', taskNode);
if (whens) {
for ( const when of whens.items){
const input = findNodeByKey<YamlNode>('input', when as YamlMap);
if (input) {
const inputVal = _.trim(input.raw, '"');
const whenDec = {id: `${taskName}:${inputVal}`, kind: 'When', position: input.startPosition} as DeclaredTask;

const runAfter = [];
const runAfterMap = extractTaskName(inputVal);
if (runAfterMap) {
runAfter.push(...runAfterMap.map( v => v[0]));
}
runAfter.push(...getTaskRunAfter(taskNode));
whenDec.runAfter = runAfter;
tasks.push(whenDec);
}
}
}
}

function collectConditions(taskNode: YamlMap, tasks: DeclaredTask[]): void {

const conditions = findNodeByKey<YamlSequence>('conditions', taskNode);
if (conditions) {
for (const condition of conditions.items) {
const ref = findNodeByKey<YamlNode>('conditionRef', condition as YamlMap);
if (ref) {
const conditionDec = { name: _.trim(ref.raw, '"'), kind: 'Condition', position: ref.startPosition } as DeclaredTask;
const name = _.trim(ref.raw, '"');
const conditionDec = { id: name, name: name, kind: 'Condition', position: ref.startPosition } as DeclaredTask;

const runAfter = [];
const conditions = findNodeByKey<YamlSequence>('conditions', taskNode);
Expand All @@ -410,14 +456,15 @@ function collectConditions(taskNode: YamlMap, tasks: DeclaredTask[]): void {
if (fromKey) {
for (const key of fromKey.items) {
if (key.kind === 'SCALAR') {
runAfter.push(key.raw);
runAfter.push(key.raw.trim());
}
}
}
}
}
}
}
runAfter.push(...getTaskRunAfter(taskNode));
conditionDec.runAfter = runAfter;
tasks.push(conditionDec);
}
Expand All @@ -426,16 +473,8 @@ function collectConditions(taskNode: YamlMap, tasks: DeclaredTask[]): void {
}
}

function getRunAfter(taskNode: YamlMap): string[] {
function collectRunAfter(taskNode: YamlMap, name: string): string[] {
const result: string[] = [];
const runAfter = findNodeByKey<YamlSequence>('runAfter', taskNode);
if (runAfter) {
for (const run of runAfter.items) {
if (run.kind === 'SCALAR') {
result.push(_.trim(run.raw, '"'));
}
}
}

const resources = findNodeByKey<YamlMap>('resources', taskNode);
if (resources) {
Expand Down Expand Up @@ -464,9 +503,59 @@ function getRunAfter(taskNode: YamlMap): string[] {
}
}

const whens = findNodeByKey<YamlSequence>('when', taskNode);
if (whens) {
for (const when of whens.items) {
const input = getYamlMappingValue(when as YamlMap, 'input');
if (input) {
result.push(`${name}:${_.trim(input, '"')}`);
}
}
}

if (!conditions && !whens) {
const runAfter = getTaskRunAfter(taskNode);
result.push(...runAfter);
}

return result;
}

function getTaskRunAfter(taskNode: YamlMap): string[] {
const result = [];
const runAfter = findNodeByKey<YamlSequence>('runAfter', taskNode);
if (runAfter) {
for (const run of runAfter.items) {
if (run.kind === 'SCALAR') {
result.push(_.trim(run.raw.trim(), '"'));
}
}
}

return result;
}

const taskNameAndResultReg = /\$\(tasks\.(?<taskName>[a-zA-Z\d-]+)\.results\.(?<resultName>[a-zA-Z\d-]+)\)/;
const varSubstitution = /\$\([a-zA-Z\d\\.-]+\)/g;
function extractTaskName(variable: string): [string, string][] | undefined {
if (!variable) {
return undefined;
}
const result: [string, string][] = [];
// TODO: replace this with String.matchAll call, it available on Node.js 12.0.0
const vars = variable.match(varSubstitution);
if (!vars){
return undefined;
}
for (const tknVar of vars) {
const group = tknVar.match(taskNameAndResultReg)?.groups;
if (group) {
result.push([group.taskName, group.resultName]);
}
}

return result;
}

function getPipelineRunTaskState(status: YamlMap): RunState {
let result: RunState = 'Unknown';
Expand Down Expand Up @@ -549,7 +638,7 @@ function getTasksName(tasks: YamlNode[]): string[] {
if (taskNode.kind === 'MAPPING') {
const nameValue = findNodeByKey<YamlNode>('name', taskNode as YamlMap);
if (nameValue) {
result.push(nameValue.raw);
result.push(nameValue.raw.trim());
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/pipeline/pipeline-graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ suite('Tekton graph', () => {

test('Should convert tasks to node and edge', async () => {
tknDocuments.returns([{}]);
getPipelineTasks.returns([{ name: 'Foo', kind: 'Task', taskRef: 'FooTask', runAfter: [] } as DeclaredTask,
{ name: 'Bar', kind: 'Task', taskRef: 'BarTask', runAfter: ['Foo'] } as DeclaredTask]);
getPipelineTasks.returns([{id: 'Foo', name: 'Foo', kind: 'Task', taskRef: 'FooTask', runAfter: [] } as DeclaredTask,
{id: 'Bar', name: 'Bar', kind: 'Task', taskRef: 'BarTask', runAfter: ['Foo'] } as DeclaredTask]);
const result = await graph.calculatePipelineGraph({} as vscode.TextDocument);
expect(result.length).equal(3);
});
Expand Down
Loading

0 comments on commit a42acc5

Please sign in to comment.