@revenexx/studio-integrations 0.7.0 → 1.1.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/CHANGELOG.md +633 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +4 -1
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +8 -2
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +2 -6
- package/dist/runtime/components/Integrations/ExpressionInput.vue +5 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +16 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +1 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +135 -10
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +34 -8
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +12 -8
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +9 -6
- package/dist/runtime/components/Integrations/RunsTable.vue +2 -2
- package/dist/runtime/components/Integrations/StickyNoteView.vue +45 -11
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +3 -3
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +12 -3
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +5 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +79 -12
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +13 -3
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +95 -29
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +11 -10
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +37 -26
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +4 -5
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +53 -14
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +9 -4
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +2 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +9 -2
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +27 -18
- package/dist/runtime/composables/useIntegrationsNodes.js +11 -1
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +30 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +70 -29
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +7 -14
- package/dist/runtime/composables/useIntegrationsSchedules.js +80 -35
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +30 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +18 -7
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +25 -4
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +22 -1
- package/dist/runtime/data/integrationNodeCatalog.js +8 -4
- package/dist/runtime/data/loopBody.d.ts +129 -0
- package/dist/runtime/data/loopBody.js +76 -0
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/i18n/messages/de.json +62 -33
- package/dist/runtime/i18n/messages/en.json +32 -3
- package/dist/runtime/pages/integrations/credentials.vue +4 -1
- package/dist/runtime/pages/integrations/index.vue +32 -34
- package/dist/runtime/pages/integrations/runs.vue +22 -10
- package/dist/runtime/pages/integrations/schedules.vue +64 -7
- package/dist/runtime/pages/integrations/templates/[slug].vue +13 -5
- package/dist/runtime/pages/integrations/workflows/[id].vue +193 -104
- package/dist/runtime/types/integrations.d.ts +64 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +6 -1
- package/dist/runtime/utils/credentialState.js +1 -1
- package/dist/runtime/utils/localize.d.ts +34 -0
- package/dist/runtime/utils/localize.js +22 -7
- package/dist/runtime/utils/pollBudget.d.ts +1 -1
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -0
- package/dist/runtime/utils/statusStyle.d.ts +52 -0
- package/dist/runtime/utils/statusStyle.js +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { NodeManifest, WorkflowEdge, WorkflowNode } from '../types/integrations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which nodes repeat, and which edges are walked once per element (PO-277).
|
|
4
|
+
*
|
|
5
|
+
* The iterator draws its two outputs like any other pair of ports, so nothing
|
|
6
|
+
* on the canvas said that everything hanging off `item` runs once per element
|
|
7
|
+
* while everything off `done` runs once, afterwards. This module computes the
|
|
8
|
+
* repeating part; the canvases mark it (`specs/editor-loop-body.md`).
|
|
9
|
+
*
|
|
10
|
+
* **The set is the engine's, not a tidier one.** `getLoopBodyNodes`
|
|
11
|
+
* (`worker/src/dagUtils.ts`) walks forward from the `item` port and follows
|
|
12
|
+
* EVERY outgoing edge it meets — an error route, a nested iterator's `done`,
|
|
13
|
+
* an edge the author meant as "and afterwards, do this". There is no stop
|
|
14
|
+
* condition, because the loop body has no end anywhere in the workflow. The
|
|
15
|
+
* walk below is that walk, deliberately: a UI that computed the body the
|
|
16
|
+
* author *meant* would draw nothing on the node that surprises them, which is
|
|
17
|
+
* the one case this is for.
|
|
18
|
+
*
|
|
19
|
+
* **Nesting needs no special case.** The engine narrows an inner iterator's
|
|
20
|
+
* body to its outer one (`getLoopBodyNodes(inner, edges, loopBodyIds)`), but a
|
|
21
|
+
* body is closed under reachability and the inner iterator is in the outer
|
|
22
|
+
* body — so everything reachable from the inner `item` is already in the outer
|
|
23
|
+
* one, and the narrowing never removes anything. A node inside two loops is
|
|
24
|
+
* therefore simply a member of both bodies, which is what `depth` counts.
|
|
25
|
+
*/
|
|
26
|
+
/** The iterator's repeating output — the port the body hangs off. */
|
|
27
|
+
export declare const ITEM_PORT = "item";
|
|
28
|
+
/** The iterator's post-loop output — everything off it runs once, at the end. */
|
|
29
|
+
export declare const DONE_PORT = "done";
|
|
30
|
+
/**
|
|
31
|
+
* Nodes known to iterate while no manifest says so.
|
|
32
|
+
*
|
|
33
|
+
* The worker learns which nodes iterate at bundle-build time by duck-typing the
|
|
34
|
+
* implementation (`builder/src/generateEntry.ts` → `MANIFEST.iterationNodeIds`),
|
|
35
|
+
* and nothing of that reaches the studio: `GET /nodes` publishes the manifest,
|
|
36
|
+
* and no field in it says "this node repeats what is wired to it". Until one
|
|
37
|
+
* does — {@link NodeManifest.iteration}, read first by {@link manifestIterates}
|
|
38
|
+
* — the editor has to know the published iterator by name. One slug, in one
|
|
39
|
+
* place, rather than the same string spelled out at every call site.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ITERATOR_SLUGS: readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Whether a manifest describes a node that runs its `item` side per element.
|
|
44
|
+
*
|
|
45
|
+
* A manifest that carries the field at all is believed either way — `false`
|
|
46
|
+
* defeats the slug fallback. Otherwise the day the service starts publishing it,
|
|
47
|
+
* a published `iteration: false` on the very slug below would be overruled by
|
|
48
|
+
* this file, which is the one answer the field exists to give.
|
|
49
|
+
*/
|
|
50
|
+
export declare function manifestIterates(manifest: NodeManifest | null | undefined): boolean;
|
|
51
|
+
/** The blob's iterator nodes, in blob order. */
|
|
52
|
+
export declare function iteratorNodeIds(nodes: readonly WorkflowNode[]): string[];
|
|
53
|
+
/** One iterator's body: what repeats, and where it overlaps the `done` side. */
|
|
54
|
+
export interface LoopBody {
|
|
55
|
+
/** The iterator whose `item` port this body hangs off. */
|
|
56
|
+
iteratorId: string;
|
|
57
|
+
/** Every node the engine would run once per element. */
|
|
58
|
+
nodeIds: Set<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Body nodes that are ALSO reachable from `done` — the author wired the body
|
|
61
|
+
* into the part meant to run after the loop, and the engine repeats it.
|
|
62
|
+
*
|
|
63
|
+
* Only the join case is provable. Where `done` is wired nowhere, a node
|
|
64
|
+
* appended to the body is indistinguishable from a node meant to be in it, so
|
|
65
|
+
* nothing here reports it (`specs/editor-loop-body.md` § Gaps). The mark on
|
|
66
|
+
* the card is what covers that case: it says "this repeats" wherever it is
|
|
67
|
+
* true, whether or not anything can tell it was a mistake.
|
|
68
|
+
*/
|
|
69
|
+
joinedWithDone: Set<string>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One body per iterator id, in the order given.
|
|
73
|
+
*
|
|
74
|
+
* Pure on purpose — the catalogue lookup that decides what an iterator IS lives
|
|
75
|
+
* in {@link iteratorNodeIds}, so the walk can be tested against a graph alone.
|
|
76
|
+
*/
|
|
77
|
+
export declare function computeLoopBodies(iteratorIds: readonly string[], edges: readonly WorkflowEdge[]): LoopBody[];
|
|
78
|
+
/** What one node card has to say about repeating. */
|
|
79
|
+
export interface LoopBodyMark {
|
|
80
|
+
/**
|
|
81
|
+
* How many bodies this node is in — 1 for a plain loop, 2 inside a nested
|
|
82
|
+
* one. The card shows the number from 2 up: "repeats" is the news at depth 1,
|
|
83
|
+
* "repeats per element of both" is the news above it.
|
|
84
|
+
*/
|
|
85
|
+
depth: number;
|
|
86
|
+
/** The iterators, outermost first (a larger body encloses a smaller one). */
|
|
87
|
+
iteratorIds: string[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Marks for a whole canvas: which nodes repeat, and the bodies behind them.
|
|
91
|
+
*
|
|
92
|
+
* **Nodes only, deliberately — the connections carry nothing.** Tinting the
|
|
93
|
+
* body's edges was the obvious other half, and it is the one thing this could
|
|
94
|
+
* not do: `specs/editor-output-paths.md` AC-2 promises that the lines carry no
|
|
95
|
+
* meaning and stay neutral, and colour and dashing on an edge are both reserved
|
|
96
|
+
* for a run that actually happened (`docs/editor-path-colors.md`). A body drawn
|
|
97
|
+
* in a colour of its own would take that promise back for every workflow with a
|
|
98
|
+
* loop in it.
|
|
99
|
+
*/
|
|
100
|
+
export interface LoopBodyMarks {
|
|
101
|
+
byNode: Map<string, LoopBodyMark>;
|
|
102
|
+
bodies: LoopBody[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The whole computation for one blob, ready to hand to a canvas.
|
|
106
|
+
*
|
|
107
|
+
* Called by all three canvases (editor, run replay, history preview) on every
|
|
108
|
+
* sync: the marks depend on the edges, so anything less would let a rewired
|
|
109
|
+
* body keep an old mark. The cost is one pass over the edges to build the
|
|
110
|
+
* adjacency map, then two breadth-first walks per iterator over it — and a
|
|
111
|
+
* workflow with no iterator in it costs one filter over the nodes.
|
|
112
|
+
*/
|
|
113
|
+
export declare function loopBodyMarks(nodes: readonly WorkflowNode[], edges: readonly WorkflowEdge[]): LoopBodyMarks;
|
|
114
|
+
/** What one canvas hands a node card about repetition. */
|
|
115
|
+
export interface NodeLoopData {
|
|
116
|
+
/** The iterators whose body this node is in, outermost first, by label. */
|
|
117
|
+
inside?: string[];
|
|
118
|
+
/** Set on an iterator itself: how many nodes hang off its `item` port. */
|
|
119
|
+
bodySize?: number;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One call per canvas sync: node id → what its card says about repeating.
|
|
123
|
+
*
|
|
124
|
+
* Names rather than ids, because the card shows the sentence — and the same
|
|
125
|
+
* three steps a banner uses to name a node ({@link nodeDisplayLabel}), so a
|
|
126
|
+
* loop the user renamed is named that everywhere. Absent from the map means
|
|
127
|
+
* nothing to say, which is the common case and costs the card nothing.
|
|
128
|
+
*/
|
|
129
|
+
export declare function nodeLoopData(nodes: readonly WorkflowNode[], edges: readonly WorkflowEdge[]): Map<string, NodeLoopData>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { nodeDisplayLabel } from "../utils/validationFindings.js";
|
|
2
|
+
import { findManifest } from "./integrationNodeCatalog.js";
|
|
3
|
+
export const ITEM_PORT = "item";
|
|
4
|
+
export const DONE_PORT = "done";
|
|
5
|
+
export const ITERATOR_SLUGS = ["revenexx:iterator"];
|
|
6
|
+
export function manifestIterates(manifest) {
|
|
7
|
+
if (!manifest) return false;
|
|
8
|
+
if (manifest.iteration !== void 0) return manifest.iteration;
|
|
9
|
+
return ITERATOR_SLUGS.includes(manifest.slug);
|
|
10
|
+
}
|
|
11
|
+
export function iteratorNodeIds(nodes) {
|
|
12
|
+
return nodes.filter((n) => manifestIterates(findManifest(n.nodeSlug, n.nodeVersion))).map((n) => n.id);
|
|
13
|
+
}
|
|
14
|
+
function successors(edges) {
|
|
15
|
+
const out = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const edge of edges) {
|
|
17
|
+
const list = out.get(edge.from.nodeId);
|
|
18
|
+
if (list) list.push(edge.to.nodeId);
|
|
19
|
+
else out.set(edge.from.nodeId, [edge.to.nodeId]);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function reachableFromPort(fromNodeId, output, edges, next = successors(edges)) {
|
|
24
|
+
const visited = /* @__PURE__ */ new Set();
|
|
25
|
+
const queue = [];
|
|
26
|
+
for (const edge of edges) {
|
|
27
|
+
if (edge.from.nodeId === fromNodeId && edge.from.output === output) queue.push(edge.to.nodeId);
|
|
28
|
+
}
|
|
29
|
+
for (let head = 0; head < queue.length; head++) {
|
|
30
|
+
const nodeId = queue[head];
|
|
31
|
+
if (visited.has(nodeId)) continue;
|
|
32
|
+
visited.add(nodeId);
|
|
33
|
+
for (const target of next.get(nodeId) ?? []) {
|
|
34
|
+
if (!visited.has(target)) queue.push(target);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return visited;
|
|
38
|
+
}
|
|
39
|
+
export function computeLoopBodies(iteratorIds, edges) {
|
|
40
|
+
const next = successors(edges);
|
|
41
|
+
return iteratorIds.map((iteratorId) => {
|
|
42
|
+
const nodeIds = reachableFromPort(iteratorId, ITEM_PORT, edges, next);
|
|
43
|
+
const afterLoop = reachableFromPort(iteratorId, DONE_PORT, edges, next);
|
|
44
|
+
const joinedWithDone = new Set([...nodeIds].filter((id) => afterLoop.has(id)));
|
|
45
|
+
return { iteratorId, nodeIds, joinedWithDone };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function loopBodyMarks(nodes, edges) {
|
|
49
|
+
const bodies = computeLoopBodies(iteratorNodeIds(nodes), edges);
|
|
50
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
51
|
+
const outermostFirst = [...bodies].sort((a, b) => b.nodeIds.size - a.nodeIds.size);
|
|
52
|
+
for (const body of outermostFirst) {
|
|
53
|
+
for (const nodeId of body.nodeIds) {
|
|
54
|
+
const mark = byNode.get(nodeId) ?? { depth: 0, iteratorIds: [] };
|
|
55
|
+
mark.depth += 1;
|
|
56
|
+
mark.iteratorIds.push(body.iteratorId);
|
|
57
|
+
byNode.set(nodeId, mark);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { byNode, bodies };
|
|
61
|
+
}
|
|
62
|
+
export function nodeLoopData(nodes, edges) {
|
|
63
|
+
const marks = loopBodyMarks(nodes, edges);
|
|
64
|
+
if (!marks.bodies.length) return /* @__PURE__ */ new Map();
|
|
65
|
+
const labelOf = new Map(nodes.map((n) => [n.id, nodeDisplayLabel(n)]));
|
|
66
|
+
const out = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const [nodeId, mark] of marks.byNode) {
|
|
68
|
+
out.set(nodeId, { inside: mark.iteratorIds.map((id) => labelOf.get(id) ?? id) });
|
|
69
|
+
}
|
|
70
|
+
for (const body of marks.bodies) {
|
|
71
|
+
const entry = out.get(body.iteratorId) ?? {};
|
|
72
|
+
entry.bodySize = body.nodeIds.size;
|
|
73
|
+
out.set(body.iteratorId, entry);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeManifest, NodeManifestOutput, OutputKind, WorkflowEdge, WorkflowNode } from '../types/integrations.js';
|
|
1
|
+
import type { LocalizedString, NodeManifest, NodeManifestOutput, OutputKind, WorkflowEdge, WorkflowNode } from '../types/integrations.js';
|
|
2
2
|
/** `${{ nodes.<id>.outputs.<port> }}`, optionally narrowed to one field. */
|
|
3
3
|
export declare function nodeOutputExpr(nodeId: string, port: string, field?: string): string;
|
|
4
4
|
/** `${{ trigger.payload }}`, optionally narrowed to a path inside it. */
|
|
@@ -20,13 +20,70 @@ export interface FieldEntry {
|
|
|
20
20
|
name: string;
|
|
21
21
|
/** Declared data type (`string`, `number`, `boolean`, …) when the manifest carries one. */
|
|
22
22
|
dataType: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* What the publisher says this field holds, unresolved. Kept in the shape the
|
|
25
|
+
* manifest sent it so the language is chosen where it is drawn — see the note
|
|
26
|
+
* on {@link PortEntry.description}.
|
|
27
|
+
*/
|
|
28
|
+
description?: LocalizedString;
|
|
23
29
|
}
|
|
24
30
|
export interface PortEntry {
|
|
25
31
|
name: string;
|
|
26
32
|
kind: OutputKind;
|
|
27
33
|
/** Declared payload fields of this port (schema; may be empty). */
|
|
28
34
|
fields: FieldEntry[];
|
|
35
|
+
/**
|
|
36
|
+
* What the publisher says flows through this port, **unresolved** — the
|
|
37
|
+
* localised map as the manifest sent it, not a string.
|
|
38
|
+
*
|
|
39
|
+
* Resolving here would pick the language at the moment the ports are derived,
|
|
40
|
+
* and these helpers are pure by design: their tests would then depend on the
|
|
41
|
+
* studio's locale, and a language switched afterwards would not reach a port
|
|
42
|
+
* list already computed. So the raw value travels and whoever draws it calls
|
|
43
|
+
* the resolver, which is how every other manifest string in the studio is
|
|
44
|
+
* read. Absent rather than null when the publisher wrote none: most do not,
|
|
45
|
+
* and an optional key keeps the derivation's shape honest about that.
|
|
46
|
+
*/
|
|
47
|
+
description?: LocalizedString;
|
|
29
48
|
}
|
|
49
|
+
export interface InputPortEntry {
|
|
50
|
+
name: string;
|
|
51
|
+
/** Declared data type of what the node accepts here. */
|
|
52
|
+
dataType: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Whether an upstream connection is mandatory.
|
|
55
|
+
*
|
|
56
|
+
* Most manifests leave the key out, so the default decides what nearly every
|
|
57
|
+
* node claims. It is **the service's**, not ours: the node-manifest schema
|
|
58
|
+
* documents `required` as *"Whether an upstream connection is mandatory.
|
|
59
|
+
* Defaults to true."*, so an absent key is read as required and the chip says
|
|
60
|
+
* so. Reading it the other way would have the studio call a mandatory input
|
|
61
|
+
* optional on the strength of a key nobody wrote.
|
|
62
|
+
*
|
|
63
|
+
* It is a statement about what the type **declares**, not a rule anything
|
|
64
|
+
* enforces — nothing in the studio blocks a save over it, and the service's own
|
|
65
|
+
* blob validation checks that a named port exists rather than that it is fed.
|
|
66
|
+
*/
|
|
67
|
+
required: boolean;
|
|
68
|
+
/** What the publisher says the node expects, unresolved — see {@link PortEntry.description}. */
|
|
69
|
+
description?: LocalizedString;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* What a node accepts, read from the manifest's `inputs` map.
|
|
73
|
+
*
|
|
74
|
+
* **Two wire shapes, one of them a trap.** A node with named ports arrives as an
|
|
75
|
+
* object; a node that accepts nothing arrives as `[]`, because the service
|
|
76
|
+
* encodes an empty PHP array as a JSON array rather than an object. So the empty
|
|
77
|
+
* case has a different JSON *type* from the populated one, and reading it with
|
|
78
|
+
* `Object.entries` alone would work by luck rather than by intent. Both land here
|
|
79
|
+
* as an empty list.
|
|
80
|
+
*
|
|
81
|
+
* The studio read this under a singular `input` key until PO-216 — a shape the
|
|
82
|
+
* service has never sent and its schema forbids, so every input a node declared
|
|
83
|
+
* was invisible: the sentence, the data type, and whether a connection was
|
|
84
|
+
* mandatory at all.
|
|
85
|
+
*/
|
|
86
|
+
export declare function inputPortsForManifest(manifest: NodeManifest | null | undefined): InputPortEntry[];
|
|
30
87
|
/**
|
|
31
88
|
* A manifest declaring zero output ports describes a TERMINAL node (PO-201):
|
|
32
89
|
* the run ends there, so it gets no output handle and no edge may leave it.
|
|
@@ -37,13 +37,38 @@ export function collectUpstreamNodes(selectedId, nodes, edges) {
|
|
|
37
37
|
function fieldEntries(raw) {
|
|
38
38
|
if (Array.isArray(raw)) return raw.map((v) => ({ name: String(v), dataType: null }));
|
|
39
39
|
if (raw && typeof raw === "object") {
|
|
40
|
-
return Object.entries(raw).map(([name, spec]) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
return Object.entries(raw).map(([name, spec]) => {
|
|
41
|
+
const s = spec && typeof spec === "object" ? spec : null;
|
|
42
|
+
return {
|
|
43
|
+
name,
|
|
44
|
+
dataType: typeof s?.dataType === "string" ? s.dataType : null,
|
|
45
|
+
...describedBy(s?.description)
|
|
46
|
+
};
|
|
47
|
+
});
|
|
44
48
|
}
|
|
45
49
|
return [];
|
|
46
50
|
}
|
|
51
|
+
function describedBy(raw) {
|
|
52
|
+
if (typeof raw === "string") return raw.trim() ? { description: raw.trim() } : {};
|
|
53
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
54
|
+
const entries = Object.entries(raw).filter((e) => typeof e[1] === "string" && e[1].trim() !== "").map(([k, v]) => [k, v.trim()]);
|
|
55
|
+
return entries.length ? { description: Object.fromEntries(entries) } : {};
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
export function inputPortsForManifest(manifest) {
|
|
60
|
+
const raw = manifest?.inputs;
|
|
61
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
|
|
62
|
+
return Object.entries(raw).map(([name, spec]) => {
|
|
63
|
+
const s = spec && typeof spec === "object" ? spec : null;
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
dataType: typeof s?.dataType === "string" ? s.dataType : null,
|
|
67
|
+
required: s?.required !== false,
|
|
68
|
+
...describedBy(s?.description)
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
47
72
|
export function isTerminalManifest(manifest) {
|
|
48
73
|
return !!manifest && Array.isArray(manifest.outputs) && !manifest.outputs.length;
|
|
49
74
|
}
|
|
@@ -53,7 +78,7 @@ export function portsForNode(node, manifest, resolvedOutputs) {
|
|
|
53
78
|
const ports = [];
|
|
54
79
|
for (const o of list) {
|
|
55
80
|
if (o.name) {
|
|
56
|
-
ports.push({ name: o.name, kind: o.kind, fields: fieldEntries(o.fields) });
|
|
81
|
+
ports.push({ name: o.name, kind: o.kind, fields: fieldEntries(o.fields), ...describedBy(o.description) });
|
|
57
82
|
} else if (o.sourceFromConfig) {
|
|
58
83
|
const raw = node.config?.[o.sourceFromConfig];
|
|
59
84
|
const values = Array.isArray(raw) ? raw : [];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
|
+
import type { TriggerType } from '../types/integrations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Trigger type → glyph, for every surface that draws one.
|
|
5
|
+
*
|
|
6
|
+
* Eight files drew a trigger's glyph, and they did it two ways. Five spelled the
|
|
7
|
+
* mapping out — the canvas card and the run replay panel as ternaries, the node
|
|
8
|
+
* inspector's header, the template card and the template detail page as records —
|
|
9
|
+
* and three did not carry a mapping at all: they resolved a glyph from Lucide by
|
|
10
|
+
* *name*, so no import mentioned one, in the node palette's trigger tiles (through
|
|
11
|
+
* `nodeIcon`), the create dialog's trigger select and the template flow preview
|
|
12
|
+
* (each through a local `iconFor`).
|
|
13
|
+
*
|
|
14
|
+
* They did not agree: a webhook was a globe on the templates page, a link on the
|
|
15
|
+
* canvas and Lucide's pretzel in the palette, so the same trigger changed shape
|
|
16
|
+
* three times on the way from picking it to seeing it. The replay panel's ternary
|
|
17
|
+
* had no `event` branch at all, so an event-triggered run drew the manual play
|
|
18
|
+
* button — wrong information, not a mismatch (PO-329).
|
|
19
|
+
*
|
|
20
|
+
* One map instead, and the reason it can be Solar's: `TRIGGER_TEMPLATES` is
|
|
21
|
+
* this repository's own catalogue, not a node manifest, so its `icon` strings
|
|
22
|
+
* ('Play', 'Clock', 'Webhook', 'Zap') were Lucide names by habit rather than by
|
|
23
|
+
* contract. The node-type icon map (`nodeIcons.ts`) is a different thing and
|
|
24
|
+
* stays Lucide deliberately — see `docs/icons.md`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const TRIGGER_GLYPHS: Record<TriggerType, Component>;
|
|
27
|
+
/**
|
|
28
|
+
* The glyph for a trigger type.
|
|
29
|
+
*
|
|
30
|
+
* The parameter is deliberately wider than `TriggerType`: some callers read the
|
|
31
|
+
* type out of a template blob or a run record, where the service may name a kind
|
|
32
|
+
* the studio has no word for yet — `templates/[slug].vue` is built for exactly
|
|
33
|
+
* that case and says so.
|
|
34
|
+
*
|
|
35
|
+
* Such a kind falls back to the **bolt**, not to the manual play button, which is
|
|
36
|
+
* what the templates surfaces did before this map existed. A play button asserts
|
|
37
|
+
* "you start this yourself", and for an unknown kind that is a claim rather than a
|
|
38
|
+
* gap; the bolt says only that something fires it.
|
|
39
|
+
*
|
|
40
|
+
* `Object.hasOwn`, not `?? Bolt` on its own: a plain object inherits
|
|
41
|
+
* `Object.prototype`, so a service that named a trigger kind `constructor` or
|
|
42
|
+
* `toString` would resolve to a function, the `??` would never fire, and the card
|
|
43
|
+
* would try to render `<component :is="Object">`. The `Lucide[name]` lookups this
|
|
44
|
+
* map replaced could not do that — a module namespace object has a null prototype
|
|
45
|
+
* — so the guard is what keeps the docstring above true rather than nearly true.
|
|
46
|
+
*/
|
|
47
|
+
export declare function triggerGlyph(type?: string | null): Component;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Bolt, ClockCircle, LinkRound, Play } from "#solar-icons";
|
|
2
|
+
export const TRIGGER_GLYPHS = {
|
|
3
|
+
manual: Play,
|
|
4
|
+
schedule: ClockCircle,
|
|
5
|
+
webhook: LinkRound,
|
|
6
|
+
event: Bolt
|
|
7
|
+
};
|
|
8
|
+
export function triggerGlyph(type) {
|
|
9
|
+
const key = String(type);
|
|
10
|
+
return Object.hasOwn(TRIGGER_GLYPHS, key) ? TRIGGER_GLYPHS[key] : Bolt;
|
|
11
|
+
}
|