@octaviaflow/flow-rules 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3010 -28
- package/package.json +14 -5
- package/dist/catalog/actions/console-log/config.js +0 -70
- package/dist/catalog/actions/console-log/index.js +0 -89
- package/dist/catalog/actions/delay/config.js +0 -79
- package/dist/catalog/actions/delay/index.js +0 -71
- package/dist/catalog/actions/delay/migrate.js +0 -38
- package/dist/catalog/actions/delay/types.js +0 -17
- package/dist/catalog/actions/http-request/config.js +0 -111
- package/dist/catalog/actions/http-request/index.js +0 -223
- package/dist/catalog/ai/ai-agent/index.js +0 -72
- package/dist/catalog/ai/idp-extract/index.js +0 -90
- package/dist/catalog/api/graphql-query/config.js +0 -31
- package/dist/catalog/api/graphql-query/index.js +0 -75
- package/dist/catalog/communication/email-send/config.js +0 -80
- package/dist/catalog/communication/email-send/index.js +0 -152
- package/dist/catalog/communication/google-chat/config.js +0 -28
- package/dist/catalog/communication/google-chat/index.js +0 -36
- package/dist/catalog/communication/slack-message/config.js +0 -36
- package/dist/catalog/communication/slack-message/index.js +0 -37
- package/dist/catalog/communication/teams-message/config.js +0 -29
- package/dist/catalog/communication/teams-message/index.js +0 -39
- package/dist/catalog/communication/webhook-call/config.js +0 -31
- package/dist/catalog/communication/webhook-call/index.js +0 -43
- package/dist/catalog/communication/whatsapp-send/config.js +0 -36
- package/dist/catalog/communication/whatsapp-send/index.js +0 -57
- package/dist/catalog/general/query/config.js +0 -257
- package/dist/catalog/general/query/index.js +0 -271
- package/dist/catalog/general/query/types.js +0 -82
- package/dist/catalog/general/transform/config.js +0 -95
- package/dist/catalog/general/transform/index.js +0 -63
- package/dist/catalog/index.js +0 -168
- package/dist/catalog/logic/condition/config.js +0 -93
- package/dist/catalog/logic/condition/index.js +0 -64
- package/dist/catalog/logic/for-each/config.js +0 -75
- package/dist/catalog/logic/for-each/index.js +0 -92
- package/dist/catalog/logic/for-each/pathOptions.js +0 -151
- package/dist/catalog/logic/group/index.js +0 -46
- package/dist/catalog/policies.js +0 -62
- package/dist/catalog/storage/csv-action/index.js +0 -38
- package/dist/catalog/storage/dropbox-action/index.js +0 -33
- package/dist/catalog/storage/excel-action/index.js +0 -32
- package/dist/catalog/storage/file-action/index.js +0 -119
- package/dist/catalog/storage/file-operation/config.js +0 -45
- package/dist/catalog/storage/file-operation/index.js +0 -111
- package/dist/catalog/storage/ftp-action/index.js +0 -33
- package/dist/catalog/storage/google-drive-action/index.js +0 -38
- package/dist/catalog/storage/json-action/index.js +0 -31
- package/dist/catalog/storage/onedrive-action/index.js +0 -33
- package/dist/catalog/storage/s3-action/index.js +0 -36
- package/dist/catalog/storage/storage-action/index.js +0 -72
- package/dist/catalog/storage/storageCapabilities.js +0 -207
- package/dist/catalog/storage/xml-action/index.js +0 -31
- package/dist/catalog/transforms/format/config.js +0 -19
- package/dist/catalog/transforms/format/index.js +0 -39
- package/dist/catalog/triggers/connector-trigger/index.js +0 -72
- package/dist/catalog/triggers/manual-trigger/config.js +0 -60
- package/dist/catalog/triggers/manual-trigger/index.js +0 -74
- package/dist/catalog/triggers/schedule-trigger/config.js +0 -42
- package/dist/catalog/triggers/schedule-trigger/index.js +0 -102
- package/dist/catalog/triggers/webhook-trigger/config.js +0 -27
- package/dist/catalog/triggers/webhook-trigger/index.js +0 -89
- package/dist/catalog/types.js +0 -135
- package/dist/catalog/utility/debug/config.js +0 -90
- package/dist/catalog/utility/debug/index.js +0 -38
- package/dist/catalog/utility/error/config.js +0 -25
- package/dist/catalog/utility/error/index.js +0 -46
- package/dist/catalog/utility/variables/config.js +0 -70
- package/dist/catalog/utility/variables/index.js +0 -40
- package/dist/catalog/workflow.js +0 -25
- package/dist/ids.js +0 -53
- package/dist/rules/nodeConfigStatus.js +0 -217
- package/dist/rules/workflowValidator.js +0 -358
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflow Validation System — rules engine v2 (DEV-220)
|
|
3
|
-
*
|
|
4
|
-
* Validates nodes, edges, and overall workflow structure on every
|
|
5
|
-
* STRUCTURAL canvas change (WorkflowContext applies it selectively —
|
|
6
|
-
* selection/viewport/drag churn never re-runs it). Results feed:
|
|
7
|
-
* - the FlowToolbarIssues indicator (badge + hover overlay)
|
|
8
|
-
* - per-node error/warning badges (flowAdapter → DS BaseNode status)
|
|
9
|
-
* - activation gating (useFlowLifecycle: errors block Activate;
|
|
10
|
-
* Save is NEVER blocked; warnings never block anything)
|
|
11
|
-
*
|
|
12
|
-
* Severity model (ADR-flow-pause-and-rules-v2):
|
|
13
|
-
* error → the flow cannot work as built (missing trigger, broken edge,
|
|
14
|
-
* cycle, missing required config). Blocks Activate.
|
|
15
|
-
* warning → it will run, but probably not the way the user thinks
|
|
16
|
-
* ("won't run" orphans, multi-flow canvas, duplicate edges).
|
|
17
|
-
*/
|
|
18
|
-
import { getNodeConfigStatus } from './nodeConfigStatus';
|
|
19
|
-
const nodeName = (n) => n.label || n.id;
|
|
20
|
-
/**
|
|
21
|
-
* Validate a single node.
|
|
22
|
-
*/
|
|
23
|
-
export function validateNode(node, allNodes, allEdges) {
|
|
24
|
-
const errors = [];
|
|
25
|
-
// Check required fields
|
|
26
|
-
if (!node.id || node.id.trim().length === 0) {
|
|
27
|
-
errors.push({
|
|
28
|
-
id: `node-${node.id}-id`,
|
|
29
|
-
type: 'node',
|
|
30
|
-
severity: 'error',
|
|
31
|
-
message: 'Node ID is required',
|
|
32
|
-
nodeId: node.id,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
if (!node.label || node.label.trim().length === 0) {
|
|
36
|
-
errors.push({
|
|
37
|
-
id: `node-${node.id}-label`,
|
|
38
|
-
type: 'node',
|
|
39
|
-
severity: 'error',
|
|
40
|
-
message: 'Node label is required',
|
|
41
|
-
nodeId: node.id,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
// Config completeness — computed LIVE from the action definition's
|
|
45
|
-
// required fields (the persisted `isValid` flag goes stale between the
|
|
46
|
-
// loader and the current edit). Missing required fields are an ERROR and
|
|
47
|
-
// are named so the issues list is actionable.
|
|
48
|
-
const cfg = getNodeConfigStatus(node);
|
|
49
|
-
if (cfg.missingRequired.length > 0) {
|
|
50
|
-
const fields = cfg.missingRequired.slice(0, 4).join(', ');
|
|
51
|
-
const more = cfg.missingRequired.length > 4 ? ` +${cfg.missingRequired.length - 4} more` : '';
|
|
52
|
-
errors.push({
|
|
53
|
-
id: `node-${node.id}-config`,
|
|
54
|
-
type: 'node',
|
|
55
|
-
severity: 'error',
|
|
56
|
-
// Actionable for END USERS: name the exact fields AND the fix action.
|
|
57
|
-
message: `Missing required settings: ${fields}${more}. Open the step and fill them in.`,
|
|
58
|
-
nodeId: node.id,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
// Trigger nodes must have at least one outgoing connection to be useful
|
|
62
|
-
const hasOutput = allEdges.some(edge => edge.source === node.id);
|
|
63
|
-
if (!hasOutput && node.type === 'trigger') {
|
|
64
|
-
errors.push({
|
|
65
|
-
id: `node-${node.id}-output`,
|
|
66
|
-
type: 'node',
|
|
67
|
-
severity: 'warning',
|
|
68
|
-
message: `Trigger "${nodeName(node)}" has no outgoing connections`,
|
|
69
|
-
nodeId: node.id,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
return errors;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Validate a single edge. `allEdges` powers real duplicate detection —
|
|
76
|
-
* only the SECOND-and-later instance of a duplicate is flagged, so one
|
|
77
|
-
* duplicate pair yields one warning, not two.
|
|
78
|
-
*/
|
|
79
|
-
export function validateEdge(edge, allNodes, allEdges = []) {
|
|
80
|
-
const errors = [];
|
|
81
|
-
// Check if source node exists
|
|
82
|
-
const sourceNode = allNodes.find(n => n.id === edge.source);
|
|
83
|
-
if (!sourceNode) {
|
|
84
|
-
errors.push({
|
|
85
|
-
id: `edge-${edge.id}-source`,
|
|
86
|
-
type: 'edge',
|
|
87
|
-
severity: 'error',
|
|
88
|
-
message: `Source node "${edge.source}" does not exist`,
|
|
89
|
-
edgeId: edge.id,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
// Check if target node exists
|
|
93
|
-
const targetNode = allNodes.find(n => n.id === edge.target);
|
|
94
|
-
if (!targetNode) {
|
|
95
|
-
errors.push({
|
|
96
|
-
id: `edge-${edge.id}-target`,
|
|
97
|
-
type: 'edge',
|
|
98
|
-
severity: 'error',
|
|
99
|
-
message: `Target node "${edge.target}" does not exist`,
|
|
100
|
-
edgeId: edge.id,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
// Check for self-loops
|
|
104
|
-
if (edge.source === edge.target) {
|
|
105
|
-
errors.push({
|
|
106
|
-
id: `edge-${edge.id}-loop`,
|
|
107
|
-
type: 'edge',
|
|
108
|
-
severity: 'error',
|
|
109
|
-
message: 'Nodes cannot connect to themselves',
|
|
110
|
-
edgeId: edge.id,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
// Duplicate edges: same source/target (and same handles when present).
|
|
114
|
-
// (v1 shipped with `duplicateCount = 1` hard-coded — the rule could
|
|
115
|
-
// never fire. DEV-220 fixed it.)
|
|
116
|
-
const key = (e) => `${e.source}|${e.sourceHandle ?? ''}→${e.target}|${e.targetHandle ?? ''}`;
|
|
117
|
-
const myKey = key(edge);
|
|
118
|
-
const firstIndex = allEdges.findIndex(e => key(e) === myKey);
|
|
119
|
-
const myIndex = allEdges.findIndex(e => e.id === edge.id);
|
|
120
|
-
if (firstIndex !== -1 && myIndex > firstIndex) {
|
|
121
|
-
errors.push({
|
|
122
|
-
id: `edge-${edge.id}-duplicate`,
|
|
123
|
-
type: 'edge',
|
|
124
|
-
severity: 'warning',
|
|
125
|
-
message: `Duplicate connection between "${sourceNode ? nodeName(sourceNode) : edge.source}" and "${targetNode ? nodeName(targetNode) : edge.target}"`,
|
|
126
|
-
edgeId: edge.id,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return errors;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Validate entire workflow.
|
|
133
|
-
*/
|
|
134
|
-
export function validateWorkflow(nodes, edges) {
|
|
135
|
-
const allErrors = [];
|
|
136
|
-
// Check for at least one trigger node
|
|
137
|
-
const hasTrigger = nodes.some(node => node.type === 'trigger');
|
|
138
|
-
if (!hasTrigger) {
|
|
139
|
-
allErrors.push({
|
|
140
|
-
id: 'workflow-trigger',
|
|
141
|
-
type: 'workflow',
|
|
142
|
-
severity: 'error',
|
|
143
|
-
message: 'Workflow must have at least one trigger node',
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
// Check for at least one action node
|
|
147
|
-
const hasAction = nodes.some(node => node.type === 'action');
|
|
148
|
-
if (!hasAction && nodes.length > 0) {
|
|
149
|
-
allErrors.push({
|
|
150
|
-
id: 'workflow-action',
|
|
151
|
-
type: 'workflow',
|
|
152
|
-
severity: 'warning',
|
|
153
|
-
message: 'Workflow should have at least one action node',
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
// Validate individual nodes
|
|
157
|
-
nodes.forEach(node => {
|
|
158
|
-
const nodeErrors = validateNode(node, nodes, edges);
|
|
159
|
-
allErrors.push(...nodeErrors);
|
|
160
|
-
});
|
|
161
|
-
// Validate individual edges
|
|
162
|
-
edges.forEach(edge => {
|
|
163
|
-
const edgeErrors = validateEdge(edge, nodes, edges);
|
|
164
|
-
allErrors.push(...edgeErrors);
|
|
165
|
-
});
|
|
166
|
-
// ── Cycle detection (DEV-220) ──
|
|
167
|
-
// Kahn's algorithm over the execution edges; anything the topological
|
|
168
|
-
// sort can't reach sits on (or strictly behind) a cycle. The Engine
|
|
169
|
-
// APPENDS such steps to the order silently, so a cycle produces
|
|
170
|
-
// undefined ordering at run time — an error here, not a curiosity.
|
|
171
|
-
if (nodes.length > 0 && edges.length > 0) {
|
|
172
|
-
const inDegree = new Map(nodes.map(n => [n.id, 0]));
|
|
173
|
-
const out = new Map();
|
|
174
|
-
for (const e of edges) {
|
|
175
|
-
if (e.source === e.target)
|
|
176
|
-
continue; // self-loops flagged per-edge already
|
|
177
|
-
if (!inDegree.has(e.source) || !inDegree.has(e.target))
|
|
178
|
-
continue; // dangling flagged already
|
|
179
|
-
inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);
|
|
180
|
-
const list = out.get(e.source);
|
|
181
|
-
if (list)
|
|
182
|
-
list.push(e.target);
|
|
183
|
-
else
|
|
184
|
-
out.set(e.source, [e.target]);
|
|
185
|
-
}
|
|
186
|
-
const queue = nodes.filter(n => (inDegree.get(n.id) ?? 0) === 0).map(n => n.id);
|
|
187
|
-
let visited = 0;
|
|
188
|
-
while (queue.length) {
|
|
189
|
-
const cur = queue.shift();
|
|
190
|
-
visited++;
|
|
191
|
-
for (const next of out.get(cur) ?? []) {
|
|
192
|
-
const d = (inDegree.get(next) ?? 1) - 1;
|
|
193
|
-
inDegree.set(next, d);
|
|
194
|
-
if (d === 0)
|
|
195
|
-
queue.push(next);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
if (visited < nodes.length) {
|
|
199
|
-
const stuck = nodes.filter(n => (inDegree.get(n.id) ?? 0) > 0);
|
|
200
|
-
const names = stuck.slice(0, 3).map(nodeName).join(', ');
|
|
201
|
-
const more = stuck.length > 3 ? ` +${stuck.length - 3} more` : '';
|
|
202
|
-
allErrors.push({
|
|
203
|
-
id: 'workflow-cycle',
|
|
204
|
-
type: 'workflow',
|
|
205
|
-
severity: 'error',
|
|
206
|
-
message: `Circular connection detected involving: ${names}${more}. Break the loop — execution order is undefined inside a cycle.`,
|
|
207
|
-
nodeId: stuck[0]?.id,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// ── DEV-207: "won't run" warnings ──
|
|
212
|
-
// Execution is reachability-from-entry: a node runs only if it is reachable,
|
|
213
|
-
// following edges, from a trigger. Flag configured nodes that WON'T run so
|
|
214
|
-
// the consequence is explicit (a stray, unconnected Slack step silently not
|
|
215
|
-
// firing is the exact footgun this prevents).
|
|
216
|
-
const isTrigger = (n) => n.type === 'trigger' || String(n.actionDefinitionId ?? '').endsWith('_trigger');
|
|
217
|
-
const triggerIds = nodes.filter(isTrigger).map(n => n.id);
|
|
218
|
-
// Forward reachability from every trigger, closed over parent/child so a
|
|
219
|
-
// container's body counts as reachable when the container is (mirrors the
|
|
220
|
-
// Engine's executionScope).
|
|
221
|
-
const adjacency = new Map();
|
|
222
|
-
for (const e of edges) {
|
|
223
|
-
const list = adjacency.get(e.source);
|
|
224
|
-
if (list)
|
|
225
|
-
list.push(e.target);
|
|
226
|
-
else
|
|
227
|
-
adjacency.set(e.source, [e.target]);
|
|
228
|
-
}
|
|
229
|
-
const reachable = new Set(triggerIds);
|
|
230
|
-
const queue = [...triggerIds];
|
|
231
|
-
while (queue.length) {
|
|
232
|
-
const cur = queue.shift();
|
|
233
|
-
for (const next of adjacency.get(cur) ?? []) {
|
|
234
|
-
if (!reachable.has(next)) {
|
|
235
|
-
reachable.add(next);
|
|
236
|
-
queue.push(next);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
let changed = true;
|
|
241
|
-
while (changed) {
|
|
242
|
-
changed = false;
|
|
243
|
-
for (const n of nodes) {
|
|
244
|
-
if (reachable.has(n.id)) {
|
|
245
|
-
if (n.parentId && !reachable.has(n.parentId)) {
|
|
246
|
-
reachable.add(n.parentId);
|
|
247
|
-
changed = true;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
else if (n.parentId && reachable.has(n.parentId)) {
|
|
251
|
-
reachable.add(n.id);
|
|
252
|
-
changed = true;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
nodes.forEach(node => {
|
|
257
|
-
if (isTrigger(node))
|
|
258
|
-
return;
|
|
259
|
-
const isConnected = edges.some(e => e.source === node.id || e.target === node.id);
|
|
260
|
-
// With ≥1 trigger, "won't run" is precise: not reachable from any trigger.
|
|
261
|
-
// With NO trigger yet (a draft), fall back to the connectivity check so we
|
|
262
|
-
// still flag a fully-orphaned node without spamming every step of a
|
|
263
|
-
// trigger-less work-in-progress.
|
|
264
|
-
const wontRun = triggerIds.length > 0 ? !reachable.has(node.id) : !isConnected;
|
|
265
|
-
if (wontRun) {
|
|
266
|
-
allErrors.push({
|
|
267
|
-
id: `node-${node.id}-orphan`,
|
|
268
|
-
type: 'node',
|
|
269
|
-
severity: 'warning',
|
|
270
|
-
message: isConnected
|
|
271
|
-
? `"${nodeName(node)}" isn't reachable from a trigger and won't run.`
|
|
272
|
-
: `"${nodeName(node)}" isn't connected to a flow and won't run.`,
|
|
273
|
-
nodeId: node.id,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
// ── Multi-flow canvas advisory (DEV-220 / ADR multi-graph decision) ──
|
|
278
|
-
// Multiple disconnected trigger trees on one canvas are LEGAL (component-
|
|
279
|
-
// scoped execution isolates them), but usually a maintainability smell —
|
|
280
|
-
// warn, never block. Components are weakly-connected (undirected) with the
|
|
281
|
-
// same parent/child closure as reachability.
|
|
282
|
-
if (triggerIds.length > 1) {
|
|
283
|
-
const parentOf = new Map(nodes.filter(n => n.parentId).map(n => [n.id, n.parentId]));
|
|
284
|
-
const undirected = new Map();
|
|
285
|
-
const link = (a, b) => {
|
|
286
|
-
const la = undirected.get(a);
|
|
287
|
-
if (la)
|
|
288
|
-
la.push(b);
|
|
289
|
-
else
|
|
290
|
-
undirected.set(a, [b]);
|
|
291
|
-
};
|
|
292
|
-
for (const e of edges) {
|
|
293
|
-
link(e.source, e.target);
|
|
294
|
-
link(e.target, e.source);
|
|
295
|
-
}
|
|
296
|
-
for (const [child, parent] of parentOf) {
|
|
297
|
-
link(child, parent);
|
|
298
|
-
link(parent, child);
|
|
299
|
-
}
|
|
300
|
-
const seen = new Set();
|
|
301
|
-
let flowsWithTrigger = 0;
|
|
302
|
-
for (const t of triggerIds) {
|
|
303
|
-
if (seen.has(t))
|
|
304
|
-
continue;
|
|
305
|
-
flowsWithTrigger++;
|
|
306
|
-
const stack = [t];
|
|
307
|
-
seen.add(t);
|
|
308
|
-
while (stack.length) {
|
|
309
|
-
const cur = stack.pop();
|
|
310
|
-
for (const next of undirected.get(cur) ?? []) {
|
|
311
|
-
if (!seen.has(next)) {
|
|
312
|
-
seen.add(next);
|
|
313
|
-
stack.push(next);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
if (flowsWithTrigger > 1) {
|
|
319
|
-
allErrors.push({
|
|
320
|
-
id: 'workflow-multi-flow',
|
|
321
|
-
type: 'workflow',
|
|
322
|
-
severity: 'warning',
|
|
323
|
-
message: `This canvas holds ${flowsWithTrigger} separate flows. Each trigger runs only its own flow — consider splitting them into separate flows for clarity.`,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
const errors = allErrors.filter(e => e.severity === 'error');
|
|
328
|
-
const warnings = allErrors.filter(e => e.severity === 'warning');
|
|
329
|
-
return {
|
|
330
|
-
isValid: errors.length === 0,
|
|
331
|
-
errors,
|
|
332
|
-
warnings,
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Get validation status for a specific node
|
|
337
|
-
*/
|
|
338
|
-
export function getNodeValidationStatus(nodeId, validationResult) {
|
|
339
|
-
const hasError = validationResult.errors.some(e => e.nodeId === nodeId);
|
|
340
|
-
if (hasError)
|
|
341
|
-
return 'error';
|
|
342
|
-
const hasWarning = validationResult.warnings.some(e => e.nodeId === nodeId);
|
|
343
|
-
if (hasWarning)
|
|
344
|
-
return 'warning';
|
|
345
|
-
return 'valid';
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Get validation status for a specific edge
|
|
349
|
-
*/
|
|
350
|
-
export function getEdgeValidationStatus(edgeId, validationResult) {
|
|
351
|
-
const hasError = validationResult.errors.some(e => e.edgeId === edgeId);
|
|
352
|
-
if (hasError)
|
|
353
|
-
return 'error';
|
|
354
|
-
const hasWarning = validationResult.warnings.some(e => e.edgeId === edgeId);
|
|
355
|
-
if (hasWarning)
|
|
356
|
-
return 'warning';
|
|
357
|
-
return 'valid';
|
|
358
|
-
}
|