@memberjunction/ng-task-graph-editor 0.0.0 → 6.1.0-edge.2
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/LICENSE +7 -0
- package/dist/lib/task-graph-canvas-adapter.d.ts +195 -0
- package/dist/lib/task-graph-canvas-adapter.d.ts.map +1 -0
- package/dist/lib/task-graph-canvas-adapter.js +380 -0
- package/dist/lib/task-graph-canvas-adapter.js.map +1 -0
- package/dist/lib/task-graph-editor-events.d.ts +137 -0
- package/dist/lib/task-graph-editor-events.d.ts.map +1 -0
- package/dist/lib/task-graph-editor-events.js +180 -0
- package/dist/lib/task-graph-editor-events.js.map +1 -0
- package/dist/lib/task-graph-editor.component.d.ts +230 -0
- package/dist/lib/task-graph-editor.component.d.ts.map +1 -0
- package/dist/lib/task-graph-editor.component.js +597 -0
- package/dist/lib/task-graph-editor.component.js.map +1 -0
- package/dist/lib/task-graph-editor.module.d.ts +14 -0
- package/dist/lib/task-graph-editor.module.d.ts.map +1 -0
- package/dist/lib/task-graph-editor.module.js +24 -0
- package/dist/lib/task-graph-editor.module.js.map +1 -0
- package/dist/lib/task-graph-properties-panel.component.d.ts +97 -0
- package/dist/lib/task-graph-properties-panel.component.d.ts.map +1 -0
- package/dist/lib/task-graph-properties-panel.component.js +357 -0
- package/dist/lib/task-graph-properties-panel.component.js.map +1 -0
- package/dist/lib/task-graph-run-view.component.d.ts +113 -0
- package/dist/lib/task-graph-run-view.component.d.ts.map +1 -0
- package/dist/lib/task-graph-run-view.component.js +289 -0
- package/dist/lib/task-graph-run-view.component.js.map +1 -0
- package/dist/lib/task-graph-runtime-source.d.ts +55 -0
- package/dist/lib/task-graph-runtime-source.d.ts.map +1 -0
- package/dist/lib/task-graph-runtime-source.js +66 -0
- package/dist/lib/task-graph-runtime-source.js.map +1 -0
- package/dist/public-api.d.ts +8 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +16 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +53 -7
- package/README.md +0 -45
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Watching a graph that is running, on the canvas its author drew on.
|
|
3
|
+
*
|
|
4
|
+
* **Why this is a component and not three.** A run needs rendering in at least three places — inside
|
|
5
|
+
* an Agent Run, inside the test harness, and on a standalone workflow-runs screen — and every one of
|
|
6
|
+
* them wants the identical thing: the graph, with live status, laid out legibly, with a node you can
|
|
7
|
+
* click. Three implementations would be three chances to render the same run differently, and the
|
|
8
|
+
* one that matters most (is that branch grey because it was *not taken*, or because it *broke*?) is
|
|
9
|
+
* exactly the distinction a second implementation gets wrong.
|
|
10
|
+
*
|
|
11
|
+
* **What it owns and what it doesn't.** It owns data acquisition — reading `MJ: Tasks` and
|
|
12
|
+
* `MJ: Task Dependencies` through the host's provider — and geometry. It does not navigate: a
|
|
13
|
+
* widgets-layer component cannot know whether it sits inside Explorer, so selecting a node emits an
|
|
14
|
+
* event and the host decides what "open the agent run" means. That boundary is the whole reason
|
|
15
|
+
* this can live beside the editor rather than in Explorer.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/ng-task-graph-editor
|
|
18
|
+
*/
|
|
19
|
+
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, } from '@angular/core';
|
|
20
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
21
|
+
import { LogError, RunView } from '@memberjunction/core';
|
|
22
|
+
import { GraphLayoutBounds, LayoutGraphNodes, ProjectTaskRowsToSpec, } from '@memberjunction/ai-core-plus';
|
|
23
|
+
import { BuildRuntimeStatus } from './task-graph-runtime-source';
|
|
24
|
+
import * as i0 from "@angular/core";
|
|
25
|
+
import * as i1 from "@memberjunction/ng-ui-components";
|
|
26
|
+
import * as i2 from "./task-graph-editor.component";
|
|
27
|
+
function TaskGraphRunViewComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
28
|
+
i0.ɵɵelement(0, "mj-alert", 0);
|
|
29
|
+
} if (rf & 2) {
|
|
30
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
31
|
+
i0.ɵɵproperty("Message", ctx_r0.ErrorMessage);
|
|
32
|
+
} }
|
|
33
|
+
function TaskGraphRunViewComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
34
|
+
i0.ɵɵelementStart(0, "div", 1);
|
|
35
|
+
i0.ɵɵtext(1, "Loading the workflow\u2019s steps\u2026");
|
|
36
|
+
i0.ɵɵelementEnd();
|
|
37
|
+
} }
|
|
38
|
+
function TaskGraphRunViewComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
39
|
+
i0.ɵɵelement(0, "mj-empty-state", 2);
|
|
40
|
+
} }
|
|
41
|
+
function TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
42
|
+
i0.ɵɵelementStart(0, "span", 6);
|
|
43
|
+
i0.ɵɵtext(1);
|
|
44
|
+
i0.ɵɵelementEnd();
|
|
45
|
+
} if (rf & 2) {
|
|
46
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
47
|
+
i0.ɵɵadvance();
|
|
48
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.SkippedCount, " not taken");
|
|
49
|
+
} }
|
|
50
|
+
function TaskGraphRunViewComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
51
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
52
|
+
i0.ɵɵelementStart(0, "div", 3)(1, "div", 4)(2, "span", 5);
|
|
53
|
+
i0.ɵɵtext(3);
|
|
54
|
+
i0.ɵɵelementEnd();
|
|
55
|
+
i0.ɵɵconditionalCreate(4, TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template, 2, 1, "span", 6);
|
|
56
|
+
i0.ɵɵelementEnd();
|
|
57
|
+
i0.ɵɵelementStart(5, "div", 7)(6, "mj-task-graph-editor", 8);
|
|
58
|
+
i0.ɵɵlistener("SelectionChanged", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_SelectionChanged_6_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnSelectionChanged($event)); });
|
|
59
|
+
i0.ɵɵelementEnd()()();
|
|
60
|
+
} if (rf & 2) {
|
|
61
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
62
|
+
i0.ɵɵadvance(3);
|
|
63
|
+
i0.ɵɵtextInterpolate2("", ctx_r0.CompletedCount, " of ", ctx_r0.TotalCount, " complete");
|
|
64
|
+
i0.ɵɵadvance();
|
|
65
|
+
i0.ɵɵconditional(ctx_r0.SkippedCount > 0 ? 4 : -1);
|
|
66
|
+
i0.ɵɵadvance();
|
|
67
|
+
i0.ɵɵstyleProp("height", ctx_r0.Height);
|
|
68
|
+
i0.ɵɵadvance();
|
|
69
|
+
i0.ɵɵproperty("Spec", ctx_r0.Spec)("RuntimeStatus", ctx_r0.RuntimeStatus)("NodePositions", ctx_r0.Positions)("ReadOnly", true)("ShowToolbar", false)("ShowPalette", false)("ShowProperties", false)("ShowMinimap", false)("ShowStatusBar", false);
|
|
70
|
+
} }
|
|
71
|
+
/** Statuses at which a graph has stopped moving, so polling can stop with it. */
|
|
72
|
+
const SETTLED = new Set(['Complete', 'Failed', 'Cancelled', 'Skipped']);
|
|
73
|
+
export class TaskGraphRunViewComponent extends BaseAngularComponent {
|
|
74
|
+
cdr;
|
|
75
|
+
/**
|
|
76
|
+
* The graph to watch — the parent `MJ: Tasks` row that represents it.
|
|
77
|
+
*
|
|
78
|
+
* Setter-based rather than `ngOnChanges`, matching the editor beside it: the reaction is
|
|
79
|
+
* explicit and costs nothing on unrelated change-detection passes.
|
|
80
|
+
*/
|
|
81
|
+
set ParentTaskID(value) {
|
|
82
|
+
if (value === this.parentTaskID)
|
|
83
|
+
return;
|
|
84
|
+
this.parentTaskID = value;
|
|
85
|
+
void this.load();
|
|
86
|
+
}
|
|
87
|
+
get ParentTaskID() {
|
|
88
|
+
return this.parentTaskID;
|
|
89
|
+
}
|
|
90
|
+
/** Name shown on the compiled graph. Purely cosmetic; the rows carry the truth. */
|
|
91
|
+
WorkflowName = 'Workflow';
|
|
92
|
+
/**
|
|
93
|
+
* Keep re-reading while the graph is still moving.
|
|
94
|
+
*
|
|
95
|
+
* Off by default: most hosts show a finished run, and a component that polls by default would
|
|
96
|
+
* have every historical run quietly issuing queries forever.
|
|
97
|
+
*/
|
|
98
|
+
LiveUpdates = false;
|
|
99
|
+
/** How often to re-read while live. Seconds, not milliseconds — this is a human-scale view. */
|
|
100
|
+
PollIntervalSeconds = 3;
|
|
101
|
+
/** Height of the canvas. Hosts embed this in very different amounts of space. */
|
|
102
|
+
Height = '320px';
|
|
103
|
+
NodeSelected = new EventEmitter();
|
|
104
|
+
/** Emitted once, when every step has reached a terminal status. */
|
|
105
|
+
Settled = new EventEmitter();
|
|
106
|
+
Spec = null;
|
|
107
|
+
RuntimeStatus = null;
|
|
108
|
+
Positions = new Map();
|
|
109
|
+
IsLoading = false;
|
|
110
|
+
ErrorMessage = null;
|
|
111
|
+
/** Rows by id, so a selection event can hand the host the whole task rather than an id. */
|
|
112
|
+
taskByID = new Map();
|
|
113
|
+
parentTaskID = null;
|
|
114
|
+
pollTimer = null;
|
|
115
|
+
destroyed = false;
|
|
116
|
+
constructor(cdr) {
|
|
117
|
+
super();
|
|
118
|
+
this.cdr = cdr;
|
|
119
|
+
}
|
|
120
|
+
ngOnDestroy() {
|
|
121
|
+
this.destroyed = true;
|
|
122
|
+
this.stopPolling();
|
|
123
|
+
}
|
|
124
|
+
/** Steps in a terminal state, for the host's summary line. */
|
|
125
|
+
get CompletedCount() {
|
|
126
|
+
return [...this.taskByID.values()].filter((t) => t.Status === 'Complete').length;
|
|
127
|
+
}
|
|
128
|
+
get TotalCount() {
|
|
129
|
+
return this.taskByID.size;
|
|
130
|
+
}
|
|
131
|
+
get SkippedCount() {
|
|
132
|
+
return [...this.taskByID.values()].filter((t) => t.Status === 'Skipped').length;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A node was selected on the canvas.
|
|
136
|
+
*
|
|
137
|
+
* The projected `tempId` IS the task id, so the row lookup is exact — and handing the host the
|
|
138
|
+
* whole task rather than an id spares every host from re-reading a row this component already
|
|
139
|
+
* holds.
|
|
140
|
+
*/
|
|
141
|
+
OnSelectionChanged(args) {
|
|
142
|
+
const tempId = args?.Task?.tempId;
|
|
143
|
+
if (!tempId)
|
|
144
|
+
return;
|
|
145
|
+
this.NodeSelected.emit({ TaskID: tempId, Task: this.taskByID.get(tempId) ?? null });
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Reads the graph and projects it onto the canvas.
|
|
149
|
+
*
|
|
150
|
+
* `BypassCache` throughout: a run view exists to show what is true *now*, and the whole point of
|
|
151
|
+
* the poll is to see a status that changed a second ago. A cached read would render a stale graph
|
|
152
|
+
* that looks authoritative.
|
|
153
|
+
*/
|
|
154
|
+
async load() {
|
|
155
|
+
this.stopPolling();
|
|
156
|
+
if (!this.parentTaskID) {
|
|
157
|
+
this.Spec = null;
|
|
158
|
+
this.cdr.markForCheck();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.IsLoading = this.Spec === null; // only show the spinner on first load, not on every poll
|
|
162
|
+
this.ErrorMessage = null;
|
|
163
|
+
try {
|
|
164
|
+
const rv = RunView.FromMetadataProvider(this.ProviderToUse);
|
|
165
|
+
const [tasks, deps] = await rv.RunViews([
|
|
166
|
+
{
|
|
167
|
+
EntityName: 'MJ: Tasks',
|
|
168
|
+
ExtraFilter: `ParentID='${this.parentTaskID}'`,
|
|
169
|
+
// Started work in the order it ran, unstarted last, persist order as the
|
|
170
|
+
// tiebreak — the same rule GetAgentRunTree uses, so a graph reads identically
|
|
171
|
+
// wherever it is shown.
|
|
172
|
+
//
|
|
173
|
+
// `__mj_CreatedAt ASC` alone was persist order, which is the COMPILER's walk
|
|
174
|
+
// order, not the author's numbering and not what actually happened: a settled
|
|
175
|
+
// graph listed its steps in an order unrelated to their execution, and a skipped
|
|
176
|
+
// step could sit above work that ran.
|
|
177
|
+
OrderBy: 'CASE WHEN StartedAt IS NULL THEN 1 ELSE 0 END, StartedAt, __mj_CreatedAt',
|
|
178
|
+
ResultType: 'entity_object',
|
|
179
|
+
BypassCache: true,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
EntityName: 'MJ: Task Dependencies',
|
|
183
|
+
ExtraFilter: `TaskID IN (SELECT ID FROM __mj.Task WHERE ParentID='${this.parentTaskID}')`,
|
|
184
|
+
ResultType: 'entity_object',
|
|
185
|
+
BypassCache: true,
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
188
|
+
if (this.destroyed)
|
|
189
|
+
return;
|
|
190
|
+
if (!tasks.Success) {
|
|
191
|
+
this.fail(`The workflow's steps could not be loaded: ${tasks.ErrorMessage}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const taskRows = (tasks.Results ?? []);
|
|
195
|
+
const depRows = (deps.Success ? (deps.Results ?? []) : []);
|
|
196
|
+
this.project(taskRows, depRows);
|
|
197
|
+
if (this.LiveUpdates && !this.isSettled(taskRows))
|
|
198
|
+
this.schedulePoll();
|
|
199
|
+
else if (taskRows.length > 0)
|
|
200
|
+
this.Settled.emit();
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
this.fail(e instanceof Error ? e.message : String(e));
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
this.IsLoading = false;
|
|
207
|
+
this.cdr.markForCheck();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Rows → spec + runtime + geometry. */
|
|
211
|
+
project(taskRows, depRows) {
|
|
212
|
+
this.taskByID = new Map(taskRows.map((t) => [t.ID, t]));
|
|
213
|
+
const projection = ProjectTaskRowsToSpec(this.WorkflowName, taskRows, depRows);
|
|
214
|
+
this.Spec = projection.Spec;
|
|
215
|
+
// Correlate by ID, not by name: a projected node's tempId IS the task id, which is the one
|
|
216
|
+
// case where an id match is exact. Name correlation exists for specs whose tempIds were
|
|
217
|
+
// never real, and would be needlessly lossy here.
|
|
218
|
+
const knownIDs = new Set(taskRows.map((t) => t.ID));
|
|
219
|
+
this.RuntimeStatus = BuildRuntimeStatus(taskRows, new Map(), knownIDs);
|
|
220
|
+
this.Positions = this.resolvePositions(projection.AuthoredPositions, taskRows, depRows);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The author's arrangement where there is one, a computed layout everywhere else.
|
|
224
|
+
*
|
|
225
|
+
* Mixing the two per-node rather than choosing one for the whole graph: a workflow whose author
|
|
226
|
+
* positioned some steps and left others where they fell should keep the positions they chose.
|
|
227
|
+
* A wholly unpositioned graph — the normal case for anything an agent emitted — is laid out
|
|
228
|
+
* entirely by the algorithm.
|
|
229
|
+
*/
|
|
230
|
+
resolvePositions(authored, taskRows, depRows) {
|
|
231
|
+
const missing = taskRows.filter((t) => !authored.has(t.ID)).map((t) => t.ID);
|
|
232
|
+
if (missing.length === 0)
|
|
233
|
+
return authored;
|
|
234
|
+
const edges = depRows.map((d) => ({ From: d.DependsOnTaskID, To: d.TaskID }));
|
|
235
|
+
const computed = LayoutGraphNodes(taskRows.map((t) => t.ID), edges, { Direction: 'LR' });
|
|
236
|
+
const resolved = new Map(computed);
|
|
237
|
+
for (const [id, position] of authored)
|
|
238
|
+
resolved.set(id, position);
|
|
239
|
+
return resolved;
|
|
240
|
+
}
|
|
241
|
+
isSettled(taskRows) {
|
|
242
|
+
return taskRows.length > 0 && taskRows.every((t) => SETTLED.has(t.Status));
|
|
243
|
+
}
|
|
244
|
+
schedulePoll() {
|
|
245
|
+
this.pollTimer = setTimeout(() => void this.load(), Math.max(1, this.PollIntervalSeconds) * 1000);
|
|
246
|
+
}
|
|
247
|
+
stopPolling() {
|
|
248
|
+
if (this.pollTimer) {
|
|
249
|
+
clearTimeout(this.pollTimer);
|
|
250
|
+
this.pollTimer = null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
fail(message) {
|
|
254
|
+
LogError(`[TaskGraphRunView] ${message}`);
|
|
255
|
+
this.ErrorMessage = message;
|
|
256
|
+
this.cdr.markForCheck();
|
|
257
|
+
}
|
|
258
|
+
/** Canvas extent, so a host can size its container to the graph rather than guessing. */
|
|
259
|
+
get GraphBounds() {
|
|
260
|
+
const box = GraphLayoutBounds(this.Positions);
|
|
261
|
+
return { Width: box.Width, Height: box.Height };
|
|
262
|
+
}
|
|
263
|
+
static ɵfac = function TaskGraphRunViewComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TaskGraphRunViewComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
264
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphRunViewComponent, selectors: [["mj-task-graph-run-view"]], inputs: { ParentTaskID: "ParentTaskID", WorkflowName: "WorkflowName", LiveUpdates: "LiveUpdates", PollIntervalSeconds: "PollIntervalSeconds", Height: "Height" }, outputs: { NodeSelected: "NodeSelected", Settled: "Settled" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 1, consts: [["Variant", "error", 3, "Message"], [1, "run-view-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No steps yet", "Message", "This workflow has not created any steps."], [1, "run-view"], [1, "run-view-summary"], [1, "run-view-count"], [1, "run-view-skipped"], [1, "run-view-canvas"], [3, "SelectionChanged", "Spec", "RuntimeStatus", "NodePositions", "ReadOnly", "ShowToolbar", "ShowPalette", "ShowProperties", "ShowMinimap", "ShowStatusBar"]], template: function TaskGraphRunViewComponent_Template(rf, ctx) { if (rf & 1) {
|
|
265
|
+
i0.ɵɵconditionalCreate(0, TaskGraphRunViewComponent_Conditional_0_Template, 1, 1, "mj-alert", 0)(1, TaskGraphRunViewComponent_Conditional_1_Template, 2, 0, "div", 1)(2, TaskGraphRunViewComponent_Conditional_2_Template, 1, 0, "mj-empty-state", 2)(3, TaskGraphRunViewComponent_Conditional_3_Template, 7, 14, "div", 3);
|
|
266
|
+
} if (rf & 2) {
|
|
267
|
+
i0.ɵɵconditional(ctx.ErrorMessage ? 0 : ctx.IsLoading ? 1 : !ctx.Spec || ctx.Spec.tasks.length === 0 ? 2 : 3);
|
|
268
|
+
} }, dependencies: [i1.MJEmptyStateComponent, i1.MJAlertComponent, i2.TaskGraphEditorComponent], styles: ["[_nghost-%COMP%] { display: block; }\n\n.run-view[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: var(--mj-space-2); }\n\n.run-view-summary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-sm);\n color: var(--mj-text-secondary);\n}\n\n.run-view-count[_ngcontent-%COMP%] { font-weight: 500; }\n\n\n\n\n.run-view-skipped[_ngcontent-%COMP%] { color: var(--mj-text-tertiary); }\n\n.run-view-canvas[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n\n.run-view-loading[_ngcontent-%COMP%] {\n padding: var(--mj-space-4);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-sm);\n}"], changeDetection: 0 });
|
|
269
|
+
}
|
|
270
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TaskGraphRunViewComponent, [{
|
|
271
|
+
type: Component,
|
|
272
|
+
args: [{ standalone: false, selector: 'mj-task-graph-run-view', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (ErrorMessage) {\n <mj-alert Variant=\"error\" [Message]=\"ErrorMessage\"></mj-alert>\n} @else if (IsLoading) {\n <div class=\"run-view-loading\">Loading the workflow’s steps…</div>\n} @else if (!Spec || Spec.tasks.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No steps yet\"\n Message=\"This workflow has not created any steps.\">\n </mj-empty-state>\n} @else {\n <div class=\"run-view\">\n <div class=\"run-view-summary\">\n <span class=\"run-view-count\">{{ CompletedCount }} of {{ TotalCount }} complete</span>\n @if (SkippedCount > 0) {\n <span class=\"run-view-skipped\">{{ SkippedCount }} not taken</span>\n }\n </div>\n\n <div class=\"run-view-canvas\" [style.height]=\"Height\">\n <mj-task-graph-editor\n [Spec]=\"Spec\"\n [RuntimeStatus]=\"RuntimeStatus\"\n [NodePositions]=\"Positions\"\n [ReadOnly]=\"true\"\n [ShowToolbar]=\"false\"\n [ShowPalette]=\"false\"\n [ShowProperties]=\"false\"\n [ShowMinimap]=\"false\"\n [ShowStatusBar]=\"false\"\n (SelectionChanged)=\"OnSelectionChanged($event)\">\n </mj-task-graph-editor>\n </div>\n </div>\n}\n", styles: [":host { display: block; }\n\n.run-view { display: flex; flex-direction: column; gap: var(--mj-space-2); }\n\n.run-view-summary {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-sm);\n color: var(--mj-text-secondary);\n}\n\n.run-view-count { font-weight: 500; }\n\n/* Not-taken reads as neutral, never as a failure: a skipped branch is a normal outcome, and\n colouring it like an error sends people hunting for a bug that does not exist. */\n.run-view-skipped { color: var(--mj-text-tertiary); }\n\n.run-view-canvas {\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n\n.run-view-loading {\n padding: var(--mj-space-4);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-sm);\n}\n"] }]
|
|
273
|
+
}], () => [{ type: i0.ChangeDetectorRef }], { ParentTaskID: [{
|
|
274
|
+
type: Input
|
|
275
|
+
}], WorkflowName: [{
|
|
276
|
+
type: Input
|
|
277
|
+
}], LiveUpdates: [{
|
|
278
|
+
type: Input
|
|
279
|
+
}], PollIntervalSeconds: [{
|
|
280
|
+
type: Input
|
|
281
|
+
}], Height: [{
|
|
282
|
+
type: Input
|
|
283
|
+
}], NodeSelected: [{
|
|
284
|
+
type: Output
|
|
285
|
+
}], Settled: [{
|
|
286
|
+
type: Output
|
|
287
|
+
}] }); })();
|
|
288
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphRunViewComponent, { className: "TaskGraphRunViewComponent", filePath: "src/lib/task-graph-run-view.component.ts", lineNumber: 60 }); })();
|
|
289
|
+
//# sourceMappingURL=task-graph-run-view.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-graph-run-view.component.js","sourceRoot":"","sources":["../../src/lib/task-graph-run-view.component.ts","../../src/lib/task-graph-run-view.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Watching a graph that is running, on the canvas its author drew on.\n *\n * **Why this is a component and not three.** A run needs rendering in at least three places — inside\n * an Agent Run, inside the test harness, and on a standalone workflow-runs screen — and every one of\n * them wants the identical thing: the graph, with live status, laid out legibly, with a node you can\n * click. Three implementations would be three chances to render the same run differently, and the\n * one that matters most (is that branch grey because it was *not taken*, or because it *broke*?) is\n * exactly the distinction a second implementation gets wrong.\n *\n * **What it owns and what it doesn't.** It owns data acquisition — reading `MJ: Tasks` and\n * `MJ: Task Dependencies` through the host's provider — and geometry. It does not navigate: a\n * widgets-layer component cannot know whether it sits inside Explorer, so selecting a node emits an\n * event and the host decides what \"open the agent run\" means. That boundary is the whole reason\n * this can live beside the editor rather than in Explorer.\n *\n * @module @memberjunction/ng-task-graph-editor\n */\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n OnDestroy,\n Output,\n} from '@angular/core';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { LogError, RunView } from '@memberjunction/core';\nimport { MJTaskEntity, MJTaskDependencyEntity } from '@memberjunction/core-entities';\nimport {\n GraphLayoutBounds,\n LayoutGraphNodes,\n ProjectTaskRowsToSpec,\n type GraphLayoutEdge,\n type GraphNodePosition,\n type TaskGraphSpec,\n} from '@memberjunction/ai-core-plus';\nimport { BuildRuntimeStatus } from './task-graph-runtime-source';\nimport type { TaskGraphRuntimeStatus } from './task-graph-canvas-adapter';\nimport type { TaskGraphSelectionChangedEventArgs } from './task-graph-editor-events';\n\n/** What the host learns when someone clicks a step. */\nexport type TaskGraphRunNodeSelectedEvent = {\n /** The `MJ: Tasks` row id. */\n TaskID: string;\n Task: MJTaskEntity | null;\n};\n\n/** Statuses at which a graph has stopped moving, so polling can stop with it. */\nconst SETTLED = new Set<MJTaskEntity['Status']>(['Complete', 'Failed', 'Cancelled', 'Skipped']);\n\n@Component({\n standalone: false,\n selector: 'mj-task-graph-run-view',\n templateUrl: './task-graph-run-view.component.html',\n styleUrls: ['./task-graph-run-view.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TaskGraphRunViewComponent extends BaseAngularComponent implements OnDestroy {\n /**\n * The graph to watch — the parent `MJ: Tasks` row that represents it.\n *\n * Setter-based rather than `ngOnChanges`, matching the editor beside it: the reaction is\n * explicit and costs nothing on unrelated change-detection passes.\n */\n @Input()\n public set ParentTaskID(value: string | null) {\n if (value === this.parentTaskID) return;\n this.parentTaskID = value;\n void this.load();\n }\n public get ParentTaskID(): string | null {\n return this.parentTaskID;\n }\n\n /** Name shown on the compiled graph. Purely cosmetic; the rows carry the truth. */\n @Input() public WorkflowName: string = 'Workflow';\n\n /**\n * Keep re-reading while the graph is still moving.\n *\n * Off by default: most hosts show a finished run, and a component that polls by default would\n * have every historical run quietly issuing queries forever.\n */\n @Input() public LiveUpdates: boolean = false;\n\n /** How often to re-read while live. Seconds, not milliseconds — this is a human-scale view. */\n @Input() public PollIntervalSeconds: number = 3;\n\n /** Height of the canvas. Hosts embed this in very different amounts of space. */\n @Input() public Height: string = '320px';\n\n @Output() public NodeSelected = new EventEmitter<TaskGraphRunNodeSelectedEvent>();\n /** Emitted once, when every step has reached a terminal status. */\n @Output() public Settled = new EventEmitter<void>();\n\n public Spec: TaskGraphSpec | null = null;\n public RuntimeStatus: TaskGraphRuntimeStatus | null = null;\n public Positions: Map<string, GraphNodePosition> = new Map();\n public IsLoading = false;\n public ErrorMessage: string | null = null;\n\n /** Rows by id, so a selection event can hand the host the whole task rather than an id. */\n private taskByID = new Map<string, MJTaskEntity>();\n private parentTaskID: string | null = null;\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private destroyed = false;\n\n constructor(private cdr: ChangeDetectorRef) {\n super();\n }\n\n public ngOnDestroy(): void {\n this.destroyed = true;\n this.stopPolling();\n }\n\n /** Steps in a terminal state, for the host's summary line. */\n public get CompletedCount(): number {\n return [...this.taskByID.values()].filter((t) => t.Status === 'Complete').length;\n }\n\n public get TotalCount(): number {\n return this.taskByID.size;\n }\n\n public get SkippedCount(): number {\n return [...this.taskByID.values()].filter((t) => t.Status === 'Skipped').length;\n }\n\n /**\n * A node was selected on the canvas.\n *\n * The projected `tempId` IS the task id, so the row lookup is exact — and handing the host the\n * whole task rather than an id spares every host from re-reading a row this component already\n * holds.\n */\n public OnSelectionChanged(args: TaskGraphSelectionChangedEventArgs): void {\n const tempId = args?.Task?.tempId;\n if (!tempId) return;\n this.NodeSelected.emit({ TaskID: tempId, Task: this.taskByID.get(tempId) ?? null });\n }\n\n /**\n * Reads the graph and projects it onto the canvas.\n *\n * `BypassCache` throughout: a run view exists to show what is true *now*, and the whole point of\n * the poll is to see a status that changed a second ago. A cached read would render a stale graph\n * that looks authoritative.\n */\n private async load(): Promise<void> {\n this.stopPolling();\n if (!this.parentTaskID) {\n this.Spec = null;\n this.cdr.markForCheck();\n return;\n }\n\n this.IsLoading = this.Spec === null; // only show the spinner on first load, not on every poll\n this.ErrorMessage = null;\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const [tasks, deps] = await rv.RunViews<MJTaskEntity | MJTaskDependencyEntity>([\n {\n EntityName: 'MJ: Tasks',\n ExtraFilter: `ParentID='${this.parentTaskID}'`,\n // Started work in the order it ran, unstarted last, persist order as the\n // tiebreak — the same rule GetAgentRunTree uses, so a graph reads identically\n // wherever it is shown.\n //\n // `__mj_CreatedAt ASC` alone was persist order, which is the COMPILER's walk\n // order, not the author's numbering and not what actually happened: a settled\n // graph listed its steps in an order unrelated to their execution, and a skipped\n // step could sit above work that ran.\n OrderBy: 'CASE WHEN StartedAt IS NULL THEN 1 ELSE 0 END, StartedAt, __mj_CreatedAt',\n ResultType: 'entity_object',\n BypassCache: true,\n },\n {\n EntityName: 'MJ: Task Dependencies',\n ExtraFilter: `TaskID IN (SELECT ID FROM __mj.Task WHERE ParentID='${this.parentTaskID}')`,\n ResultType: 'entity_object',\n BypassCache: true,\n },\n ]);\n\n if (this.destroyed) return;\n\n if (!tasks.Success) {\n this.fail(`The workflow's steps could not be loaded: ${tasks.ErrorMessage}`);\n return;\n }\n\n const taskRows = (tasks.Results ?? []) as MJTaskEntity[];\n const depRows = (deps.Success ? (deps.Results ?? []) : []) as MJTaskDependencyEntity[];\n this.project(taskRows, depRows);\n\n if (this.LiveUpdates && !this.isSettled(taskRows)) this.schedulePoll();\n else if (taskRows.length > 0) this.Settled.emit();\n } catch (e) {\n this.fail(e instanceof Error ? e.message : String(e));\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n }\n\n /** Rows → spec + runtime + geometry. */\n private project(taskRows: MJTaskEntity[], depRows: MJTaskDependencyEntity[]): void {\n this.taskByID = new Map(taskRows.map((t) => [t.ID, t]));\n\n const projection = ProjectTaskRowsToSpec(this.WorkflowName, taskRows, depRows);\n this.Spec = projection.Spec;\n\n // Correlate by ID, not by name: a projected node's tempId IS the task id, which is the one\n // case where an id match is exact. Name correlation exists for specs whose tempIds were\n // never real, and would be needlessly lossy here.\n const knownIDs = new Set(taskRows.map((t) => t.ID));\n this.RuntimeStatus = BuildRuntimeStatus(taskRows, new Map(), knownIDs);\n\n this.Positions = this.resolvePositions(projection.AuthoredPositions, taskRows, depRows);\n }\n\n /**\n * The author's arrangement where there is one, a computed layout everywhere else.\n *\n * Mixing the two per-node rather than choosing one for the whole graph: a workflow whose author\n * positioned some steps and left others where they fell should keep the positions they chose.\n * A wholly unpositioned graph — the normal case for anything an agent emitted — is laid out\n * entirely by the algorithm.\n */\n private resolvePositions(\n authored: Map<string, GraphNodePosition>,\n taskRows: MJTaskEntity[],\n depRows: MJTaskDependencyEntity[],\n ): Map<string, GraphNodePosition> {\n const missing = taskRows.filter((t) => !authored.has(t.ID)).map((t) => t.ID);\n if (missing.length === 0) return authored;\n\n const edges: GraphLayoutEdge[] = depRows.map((d) => ({ From: d.DependsOnTaskID, To: d.TaskID }));\n const computed = LayoutGraphNodes(taskRows.map((t) => t.ID), edges, { Direction: 'LR' });\n\n const resolved = new Map(computed);\n for (const [id, position] of authored) resolved.set(id, position);\n return resolved;\n }\n\n private isSettled(taskRows: MJTaskEntity[]): boolean {\n return taskRows.length > 0 && taskRows.every((t) => SETTLED.has(t.Status));\n }\n\n private schedulePoll(): void {\n this.pollTimer = setTimeout(() => void this.load(), Math.max(1, this.PollIntervalSeconds) * 1000);\n }\n\n private stopPolling(): void {\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n private fail(message: string): void {\n LogError(`[TaskGraphRunView] ${message}`);\n this.ErrorMessage = message;\n this.cdr.markForCheck();\n }\n\n /** Canvas extent, so a host can size its container to the graph rather than guessing. */\n public get GraphBounds(): { Width: number; Height: number } {\n const box = GraphLayoutBounds(this.Positions);\n return { Width: box.Width, Height: box.Height };\n }\n}\n","@if (ErrorMessage) {\n <mj-alert Variant=\"error\" [Message]=\"ErrorMessage\"></mj-alert>\n} @else if (IsLoading) {\n <div class=\"run-view-loading\">Loading the workflow’s steps…</div>\n} @else if (!Spec || Spec.tasks.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No steps yet\"\n Message=\"This workflow has not created any steps.\">\n </mj-empty-state>\n} @else {\n <div class=\"run-view\">\n <div class=\"run-view-summary\">\n <span class=\"run-view-count\">{{ CompletedCount }} of {{ TotalCount }} complete</span>\n @if (SkippedCount > 0) {\n <span class=\"run-view-skipped\">{{ SkippedCount }} not taken</span>\n }\n </div>\n\n <div class=\"run-view-canvas\" [style.height]=\"Height\">\n <mj-task-graph-editor\n [Spec]=\"Spec\"\n [RuntimeStatus]=\"RuntimeStatus\"\n [NodePositions]=\"Positions\"\n [ReadOnly]=\"true\"\n [ShowToolbar]=\"false\"\n [ShowPalette]=\"false\"\n [ShowProperties]=\"false\"\n [ShowMinimap]=\"false\"\n [ShowStatusBar]=\"false\"\n (SelectionChanged)=\"OnSelectionChanged($event)\">\n </mj-task-graph-editor>\n </div>\n </div>\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Turning live `MJ: Tasks` rows into the canvas's runtime overlay.
|
|
3
|
+
*
|
|
4
|
+
* **Why this is a pure mapper and not a subscription.** A `widgets`-layer component must not own a
|
|
5
|
+
* data subscription: it cannot know which provider it is on, whose rows it may read, or when the
|
|
6
|
+
* host wants to start and stop watching. The host owns the subscription — it already has the
|
|
7
|
+
* `BaseEntity` event stream or a `RunView` — and passes the rows here to be shaped for the canvas.
|
|
8
|
+
*
|
|
9
|
+
* That split is also what makes the runtime overlay work for **both** provenances. A durable graph
|
|
10
|
+
* is watched through `MJ: Tasks` rows; an in-run flow through `AIAgentRunStep` rows. Different
|
|
11
|
+
* sources, same shape by the time they reach the canvas, so one renderer serves both — which is the
|
|
12
|
+
* whole point of the convergence.
|
|
13
|
+
*
|
|
14
|
+
* @module @memberjunction/ng-task-graph-editor
|
|
15
|
+
*/
|
|
16
|
+
import type { TaskGraphRuntimeState, TaskGraphRuntimeStatus } from './task-graph-canvas-adapter';
|
|
17
|
+
/** The minimum a row must expose to drive the overlay. Structural, so any source can satisfy it. */
|
|
18
|
+
export type TaskGraphRuntimeRow = {
|
|
19
|
+
/** Correlates the row back to a spec node. See {@link BuildRuntimeStatus} for how. */
|
|
20
|
+
ID: string;
|
|
21
|
+
Name: string;
|
|
22
|
+
Status: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Shapes live rows into the canvas's `RuntimeStatus` map.
|
|
26
|
+
*
|
|
27
|
+
* **Correlation is by name, with an ID fallback, and that ordering is deliberate.** A submitted
|
|
28
|
+
* graph's Task rows carry database IDs, while the spec on the canvas carries producer-assigned
|
|
29
|
+
* `tempId`s — the two never match, because the producer could not know real IDs at authoring time.
|
|
30
|
+
* The task's *name* is the only value that survives submission unchanged, so it is the join key. An
|
|
31
|
+
* ID match is still honored first for the case where a host is showing a graph whose `tempId`s
|
|
32
|
+
* genuinely are row IDs — which is exactly what `ConvertAgentSpecToTaskGraph` produces.
|
|
33
|
+
*
|
|
34
|
+
* Rows that match nothing are skipped rather than guessed at: a wrong node lighting up green is
|
|
35
|
+
* worse than a node that stays grey, because the first is believed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function BuildRuntimeStatus(rows: readonly TaskGraphRuntimeRow[], specTempIdsByName: ReadonlyMap<string, string>, knownTempIds: ReadonlySet<string>): TaskGraphRuntimeStatus;
|
|
38
|
+
/**
|
|
39
|
+
* Coerces a row's status string into the canvas vocabulary.
|
|
40
|
+
*
|
|
41
|
+
* Unknown values fall back to `Pending` rather than throwing. A status the UI has not heard of is a
|
|
42
|
+
* schema that moved ahead of the client — which must degrade to "we don't know yet", not to a blank
|
|
43
|
+
* canvas or an exception in a render path.
|
|
44
|
+
*/
|
|
45
|
+
export declare function NormalizeRuntimeState(status: string | null | undefined): TaskGraphRuntimeState;
|
|
46
|
+
/** Index of task name → `tempId`, for the name-based correlation above. */
|
|
47
|
+
export declare function BuildNameIndex(tasks: readonly {
|
|
48
|
+
tempId: string;
|
|
49
|
+
name: string;
|
|
50
|
+
}[]): Map<string, string>;
|
|
51
|
+
/** True once every task has reached a state nothing will move it out of. */
|
|
52
|
+
export declare function IsRuntimeSettled(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): boolean;
|
|
53
|
+
/** A one-line progress summary, for hosts that show one beside the canvas. */
|
|
54
|
+
export declare function SummarizeRuntime(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): string;
|
|
55
|
+
//# sourceMappingURL=task-graph-runtime-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-graph-runtime-source.d.ts","sourceRoot":"","sources":["../../src/lib/task-graph-runtime-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAEjG,oGAAoG;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAC9B,sFAAsF;IACtF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,SAAS,mBAAmB,EAAE,EACpC,iBAAiB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9C,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,sBAAsB,CASxB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,qBAAqB,CAa9F;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAItG;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGpG;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAQnG"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shapes live rows into the canvas's `RuntimeStatus` map.
|
|
3
|
+
*
|
|
4
|
+
* **Correlation is by name, with an ID fallback, and that ordering is deliberate.** A submitted
|
|
5
|
+
* graph's Task rows carry database IDs, while the spec on the canvas carries producer-assigned
|
|
6
|
+
* `tempId`s — the two never match, because the producer could not know real IDs at authoring time.
|
|
7
|
+
* The task's *name* is the only value that survives submission unchanged, so it is the join key. An
|
|
8
|
+
* ID match is still honored first for the case where a host is showing a graph whose `tempId`s
|
|
9
|
+
* genuinely are row IDs — which is exactly what `ConvertAgentSpecToTaskGraph` produces.
|
|
10
|
+
*
|
|
11
|
+
* Rows that match nothing are skipped rather than guessed at: a wrong node lighting up green is
|
|
12
|
+
* worse than a node that stays grey, because the first is believed.
|
|
13
|
+
*/
|
|
14
|
+
export function BuildRuntimeStatus(rows, specTempIdsByName, knownTempIds) {
|
|
15
|
+
const status = {};
|
|
16
|
+
for (const row of rows) {
|
|
17
|
+
const tempId = knownTempIds.has(row.ID) ? row.ID : specTempIdsByName.get(row.Name);
|
|
18
|
+
if (!tempId)
|
|
19
|
+
continue;
|
|
20
|
+
status[tempId] = NormalizeRuntimeState(row.Status);
|
|
21
|
+
}
|
|
22
|
+
return status;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Coerces a row's status string into the canvas vocabulary.
|
|
26
|
+
*
|
|
27
|
+
* Unknown values fall back to `Pending` rather than throwing. A status the UI has not heard of is a
|
|
28
|
+
* schema that moved ahead of the client — which must degrade to "we don't know yet", not to a blank
|
|
29
|
+
* canvas or an exception in a render path.
|
|
30
|
+
*/
|
|
31
|
+
export function NormalizeRuntimeState(status) {
|
|
32
|
+
switch (status) {
|
|
33
|
+
case 'In Progress':
|
|
34
|
+
case 'Complete':
|
|
35
|
+
case 'Failed':
|
|
36
|
+
case 'Blocked':
|
|
37
|
+
case 'Cancelled':
|
|
38
|
+
case 'Deferred':
|
|
39
|
+
case 'Pending':
|
|
40
|
+
return status;
|
|
41
|
+
default:
|
|
42
|
+
return 'Pending';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Index of task name → `tempId`, for the name-based correlation above. */
|
|
46
|
+
export function BuildNameIndex(tasks) {
|
|
47
|
+
// Last writer wins on a duplicate name. Names are not unique in a spec — only `tempId` is — so
|
|
48
|
+
// a duplicated name is inherently ambiguous; picking one deterministically beats dropping both.
|
|
49
|
+
return new Map(tasks.map((t) => [t.name, t.tempId]));
|
|
50
|
+
}
|
|
51
|
+
/** True once every task has reached a state nothing will move it out of. */
|
|
52
|
+
export function IsRuntimeSettled(status, tempIds) {
|
|
53
|
+
const terminal = new Set(['Complete', 'Failed', 'Cancelled']);
|
|
54
|
+
return tempIds.length > 0 && tempIds.every((id) => terminal.has(status[id] ?? 'Pending'));
|
|
55
|
+
}
|
|
56
|
+
/** A one-line progress summary, for hosts that show one beside the canvas. */
|
|
57
|
+
export function SummarizeRuntime(status, tempIds) {
|
|
58
|
+
const counts = new Map();
|
|
59
|
+
for (const id of tempIds) {
|
|
60
|
+
const state = status[id] ?? 'Pending';
|
|
61
|
+
counts.set(state, (counts.get(state) ?? 0) + 1);
|
|
62
|
+
}
|
|
63
|
+
const parts = [...counts.entries()].map(([state, n]) => `${n} ${state.toLowerCase()}`);
|
|
64
|
+
return `${tempIds.length} step${tempIds.length === 1 ? '' : 's'} — ${parts.join(', ')}`;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=task-graph-runtime-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-graph-runtime-source.js","sourceRoot":"","sources":["../../src/lib/task-graph-runtime-source.ts"],"names":[],"mappings":"AAyBA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAC9B,IAAoC,EACpC,iBAA8C,EAC9C,YAAiC;IAEjC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,CAAC,MAAM,CAAC,GAAG,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAiC;IACnE,QAAQ,MAAM,EAAE,CAAC;QACb,KAAK,aAAa,CAAC;QACnB,KAAK,UAAU,CAAC;QAChB,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,UAAU,CAAC;QAChB,KAAK,SAAS;YACV,OAAO,MAAM,CAAC;QAClB;YACI,OAAO,SAAS,CAAC;IACzB,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,KAAkD;IAC7E,+FAA+F;IAC/F,gGAAgG;IAChG,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,MAA8B,EAAE,OAA0B;IACvF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAwB,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IACrF,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,MAA8B,EAAE,OAA0B;IACvF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiC,CAAC;IACxD,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,IAAI,SAAS,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACvF,OAAO,GAAG,OAAO,CAAC,MAAM,QAAQ,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["/**\n * @fileoverview Turning live `MJ: Tasks` rows into the canvas's runtime overlay.\n *\n * **Why this is a pure mapper and not a subscription.** A `widgets`-layer component must not own a\n * data subscription: it cannot know which provider it is on, whose rows it may read, or when the\n * host wants to start and stop watching. The host owns the subscription — it already has the\n * `BaseEntity` event stream or a `RunView` — and passes the rows here to be shaped for the canvas.\n *\n * That split is also what makes the runtime overlay work for **both** provenances. A durable graph\n * is watched through `MJ: Tasks` rows; an in-run flow through `AIAgentRunStep` rows. Different\n * sources, same shape by the time they reach the canvas, so one renderer serves both — which is the\n * whole point of the convergence.\n *\n * @module @memberjunction/ng-task-graph-editor\n */\nimport type { TaskGraphRuntimeState, TaskGraphRuntimeStatus } from './task-graph-canvas-adapter';\n\n/** The minimum a row must expose to drive the overlay. Structural, so any source can satisfy it. */\nexport type TaskGraphRuntimeRow = {\n /** Correlates the row back to a spec node. See {@link BuildRuntimeStatus} for how. */\n ID: string;\n Name: string;\n Status: string;\n};\n\n/**\n * Shapes live rows into the canvas's `RuntimeStatus` map.\n *\n * **Correlation is by name, with an ID fallback, and that ordering is deliberate.** A submitted\n * graph's Task rows carry database IDs, while the spec on the canvas carries producer-assigned\n * `tempId`s — the two never match, because the producer could not know real IDs at authoring time.\n * The task's *name* is the only value that survives submission unchanged, so it is the join key. An\n * ID match is still honored first for the case where a host is showing a graph whose `tempId`s\n * genuinely are row IDs — which is exactly what `ConvertAgentSpecToTaskGraph` produces.\n *\n * Rows that match nothing are skipped rather than guessed at: a wrong node lighting up green is\n * worse than a node that stays grey, because the first is believed.\n */\nexport function BuildRuntimeStatus(\n rows: readonly TaskGraphRuntimeRow[],\n specTempIdsByName: ReadonlyMap<string, string>,\n knownTempIds: ReadonlySet<string>,\n): TaskGraphRuntimeStatus {\n const status: TaskGraphRuntimeStatus = {};\n\n for (const row of rows) {\n const tempId = knownTempIds.has(row.ID) ? row.ID : specTempIdsByName.get(row.Name);\n if (!tempId) continue;\n status[tempId] = NormalizeRuntimeState(row.Status);\n }\n return status;\n}\n\n/**\n * Coerces a row's status string into the canvas vocabulary.\n *\n * Unknown values fall back to `Pending` rather than throwing. A status the UI has not heard of is a\n * schema that moved ahead of the client — which must degrade to \"we don't know yet\", not to a blank\n * canvas or an exception in a render path.\n */\nexport function NormalizeRuntimeState(status: string | null | undefined): TaskGraphRuntimeState {\n switch (status) {\n case 'In Progress':\n case 'Complete':\n case 'Failed':\n case 'Blocked':\n case 'Cancelled':\n case 'Deferred':\n case 'Pending':\n return status;\n default:\n return 'Pending';\n }\n}\n\n/** Index of task name → `tempId`, for the name-based correlation above. */\nexport function BuildNameIndex(tasks: readonly { tempId: string; name: string }[]): Map<string, string> {\n // Last writer wins on a duplicate name. Names are not unique in a spec — only `tempId` is — so\n // a duplicated name is inherently ambiguous; picking one deterministically beats dropping both.\n return new Map(tasks.map((t) => [t.name, t.tempId]));\n}\n\n/** True once every task has reached a state nothing will move it out of. */\nexport function IsRuntimeSettled(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): boolean {\n const terminal = new Set<TaskGraphRuntimeState>(['Complete', 'Failed', 'Cancelled']);\n return tempIds.length > 0 && tempIds.every((id) => terminal.has(status[id] ?? 'Pending'));\n}\n\n/** A one-line progress summary, for hosts that show one beside the canvas. */\nexport function SummarizeRuntime(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): string {\n const counts = new Map<TaskGraphRuntimeState, number>();\n for (const id of tempIds) {\n const state = status[id] ?? 'Pending';\n counts.set(state, (counts.get(state) ?? 0) + 1);\n }\n const parts = [...counts.entries()].map(([state, n]) => `${n} ${state.toLowerCase()}`);\n return `${tempIds.length} step${tempIds.length === 1 ? '' : 's'} — ${parts.join(', ')}`;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './lib/task-graph-editor.module';
|
|
2
|
+
export * from './lib/task-graph-editor.component';
|
|
3
|
+
export * from './lib/task-graph-editor-events';
|
|
4
|
+
export * from './lib/task-graph-canvas-adapter';
|
|
5
|
+
export * from './lib/task-graph-properties-panel.component';
|
|
6
|
+
export * from './lib/task-graph-run-view.component';
|
|
7
|
+
export * from './lib/task-graph-runtime-source';
|
|
8
|
+
//# sourceMappingURL=public-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAQA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,qCAAqC,CAAC;AACpD,cAAc,iCAAiC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public API Surface of @memberjunction/ng-task-graph-editor
|
|
3
|
+
*
|
|
4
|
+
* A `widgets`-layer component for viewing and editing a `TaskGraphSpec` — the one graph contract
|
|
5
|
+
* shared by design-time flows and runtime task graphs. Import `TaskGraphSpec` itself from
|
|
6
|
+
* `@memberjunction/ai-core-plus`; this package deliberately does not re-export it (see the
|
|
7
|
+
* no-cross-package-re-exports rule in `.claude/rules/typescript-style.md`).
|
|
8
|
+
*/
|
|
9
|
+
export * from './lib/task-graph-editor.module';
|
|
10
|
+
export * from './lib/task-graph-editor.component';
|
|
11
|
+
export * from './lib/task-graph-editor-events';
|
|
12
|
+
export * from './lib/task-graph-canvas-adapter';
|
|
13
|
+
export * from './lib/task-graph-properties-panel.component';
|
|
14
|
+
export * from './lib/task-graph-run-view.component';
|
|
15
|
+
export * from './lib/task-graph-runtime-source';
|
|
16
|
+
//# sourceMappingURL=public-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,qCAAqC,CAAC;AACpD,cAAc,iCAAiC,CAAC","sourcesContent":["/*\n * Public API Surface of @memberjunction/ng-task-graph-editor\n *\n * A `widgets`-layer component for viewing and editing a `TaskGraphSpec` — the one graph contract\n * shared by design-time flows and runtime task graphs. Import `TaskGraphSpec` itself from\n * `@memberjunction/ai-core-plus`; this package deliberately does not re-export it (see the\n * no-cross-package-re-exports rule in `.claude/rules/typescript-style.md`).\n */\nexport * from './lib/task-graph-editor.module';\nexport * from './lib/task-graph-editor.component';\nexport * from './lib/task-graph-editor-events';\nexport * from './lib/task-graph-canvas-adapter';\nexport * from './lib/task-graph-properties-panel.component';\nexport * from './lib/task-graph-run-view.component';\nexport * from './lib/task-graph-runtime-source';\n"]}
|
package/package.json
CHANGED
|
@@ -1,10 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-task-graph-editor",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "6.1.0-edge.2",
|
|
4
|
+
"description": "MemberJunction: View and edit a TaskGraphSpec on a canvas — the one graph contract shared by design-time flows and runtime task graphs",
|
|
5
|
+
"main": "./dist/public-api.js",
|
|
6
|
+
"typings": "./dist/public-api.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"/dist"
|
|
9
|
+
],
|
|
5
10
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
"angular",
|
|
12
|
+
"task-graph",
|
|
13
|
+
"workflow",
|
|
14
|
+
"dag",
|
|
15
|
+
"graph-editor",
|
|
16
|
+
"memberjunction"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@angular/compiler": "21.1.3",
|
|
22
|
+
"@angular/compiler-cli": "21.1.3",
|
|
23
|
+
"@memberjunction/ng-test-utils": "6.1.0-edge.2",
|
|
24
|
+
"sass": "^1.97.3",
|
|
25
|
+
"vitest": "^4.0.18"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@angular/common": "^21.1.3",
|
|
29
|
+
"@angular/core": "^21.1.3",
|
|
30
|
+
"@angular/forms": "^21.1.3",
|
|
31
|
+
"@angular/platform-browser": "^21.1.3"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@memberjunction/ai-core-plus": "6.1.0-edge.2",
|
|
35
|
+
"@memberjunction/core": "6.1.0-edge.2",
|
|
36
|
+
"@memberjunction/core-entities": "6.1.0-edge.2",
|
|
37
|
+
"@memberjunction/global": "6.1.0-edge.2",
|
|
38
|
+
"@memberjunction/ng-base-types": "6.1.0-edge.2",
|
|
39
|
+
"@memberjunction/ng-flow-editor": "6.1.0-edge.2",
|
|
40
|
+
"@memberjunction/ng-ui-components": "6.1.0-edge.2",
|
|
41
|
+
"rxjs": "^7.8.2",
|
|
42
|
+
"tslib": "^2.8.1"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/MemberJunction/MJ"
|
|
48
|
+
},
|
|
49
|
+
"mjUILayer": "widgets",
|
|
50
|
+
"scripts": {
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:types": "tsc --noEmit -p tsconfig.spec.json",
|
|
53
|
+
"build": "ngc",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/README.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# @memberjunction/ng-task-graph-editor
|
|
2
|
-
|
|
3
|
-
## ⚠️ IMPORTANT NOTICE ⚠️
|
|
4
|
-
|
|
5
|
-
**This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
|
|
6
|
-
|
|
7
|
-
This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
This package exists to:
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@memberjunction/ng-task-graph-editor`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
15
|
-
|
|
16
|
-
## What is OIDC Trusted Publishing?
|
|
17
|
-
|
|
18
|
-
OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
|
|
19
|
-
|
|
20
|
-
## Setup Instructions
|
|
21
|
-
|
|
22
|
-
To properly configure OIDC trusted publishing for this package:
|
|
23
|
-
|
|
24
|
-
1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
28
|
-
|
|
29
|
-
## DO NOT USE THIS PACKAGE
|
|
30
|
-
|
|
31
|
-
This package is a placeholder for OIDC configuration only. It:
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
-
|
|
37
|
-
## More Information
|
|
38
|
-
|
|
39
|
-
For more details about npm's trusted publishing feature, see:
|
|
40
|
-
- [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
**Maintained for OIDC setup purposes only**
|