Skip to content

Commit a98d5f1

Browse files
committed
Added dag class in UI to detect any cyclic dependency
1 parent 2690dbb commit a98d5f1

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This class will help us to detect if any new edge can lead to cycle or not
2+
// Thus we can alert user to avoid that edge thus resolving cycle issue on UI
3+
4+
// Directed Acyclic Graph
5+
export class DAG {
6+
#launcher_list;
7+
#adjacency_list;
8+
#visited;
9+
#stack;
10+
#has_cycle;
11+
12+
constructor() {
13+
this.#launcher_list = [];
14+
this.#adjacency_list = {};
15+
}
16+
17+
addEdge(fromId, toId) {
18+
if(!this.#launcher_list.includes(fromId)) {
19+
this.#launcher_list.push(fromId);
20+
}
21+
22+
if(!this.#launcher_list.includes(toId)) {
23+
this.#launcher_list.push(toId);
24+
}
25+
26+
if (!this.#adjacency_list[fromId]) {
27+
this.#adjacency_list[fromId] = [];
28+
}
29+
this.#adjacency_list[fromId].push(toId);
30+
31+
this.#has_cycle = false;
32+
this.#visited = new Set();
33+
this.#stack = new Set();
34+
this.#launcher_list.forEach( l => this.#detectCycle(l) );
35+
36+
// Remove the added edges form the adjacency list
37+
if(this.#has_cycle === true) {
38+
this.#adjacency_list[fromId].pop();
39+
}
40+
}
41+
42+
hasCycle() {
43+
return this.#has_cycle;
44+
}
45+
46+
// Basic dfs on graph to find a cycle
47+
#detectCycle(id) {
48+
if(this.#stack.has(id)) {
49+
this.#has_cycle = true;
50+
return;
51+
}
52+
if(this.#visited.has(id)) {
53+
return;
54+
}
55+
56+
this.#stack.add(id);
57+
this.#visited.add(id);
58+
for (const l of this.#adjacency_list[id] || []) {
59+
this.#detectCycle(l);
60+
}
61+
62+
this.#stack.delete(id);
63+
}
64+
}

apps/dashboard/app/javascript/workflows.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DAG } from './dag.js';
2+
13
(() => {
24
const stage = document.getElementById('stage');
35
const edges_svg = document.getElementById('edges');
@@ -7,6 +9,7 @@
79
const delete_edge_button = document.getElementById('btn-delete-edge');
810
const selected_launcher = document.getElementById('select_launcher');
911
const base_launcher_url = document.getElementById('base-launcher-url').value;
12+
const dag = new DAG;
1013

1114
const boxes = new Map();
1215
const edges = [];
@@ -78,9 +81,16 @@
7881
if (existingEdge) { return; }
7982

8083
const reverseEdge = edges.find(e => e.fromId === toId && e.toId === fromId);
81-
if (reverseEdge) { alert('Bidirectional edges are not allowed.') }
84+
if (reverseEdge) {
85+
alert('Bidirectional edges are not allowed.');
86+
return;
87+
}
8288

83-
89+
dag.addEdge(fromId, toId)
90+
if (dag.hasCycle()) {
91+
alert('Adding this edge will create a cyclic dependency among the launchers.');
92+
return;
93+
}
8494

8595
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
8696
line.classList.add('edge');

0 commit comments

Comments
 (0)