@revenexx/studio-integrations 1.1.0 → 1.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/CHANGELOG.md +520 -0
- package/README.md +3 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -18
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +13 -23
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +7 -6
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +15 -4
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +77 -22
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +5 -4
- package/dist/runtime/components/Integrations/LoadingAnnouncement.d.vue.ts +38 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue +16 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue.d.ts +38 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +23 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +25 -32
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +5 -3
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +5 -17
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +30 -8
- package/dist/runtime/components/Integrations/RunResponseModal.vue +92 -24
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +30 -8
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +61 -3
- package/dist/runtime/components/Integrations/RunsTable.vue +143 -20
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +61 -3
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +10 -1
- package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -1
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +39 -28
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
- package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
- package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
- package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
- package/dist/runtime/composables/useIntegrationsTemplates.js +2 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +4 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
- package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
- package/dist/runtime/composables/useOAuthConnect.js +33 -0
- package/dist/runtime/data/expressionEditor.d.ts +17 -5
- package/dist/runtime/data/expressionEditor.js +11 -5
- package/dist/runtime/data/handleId.d.ts +61 -1
- package/dist/runtime/data/handleId.js +8 -1
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +30 -11
- package/dist/runtime/i18n/messages/en.json +27 -8
- package/dist/runtime/pages/integrations/audit.vue +7 -6
- package/dist/runtime/pages/integrations/credentials.vue +9 -21
- package/dist/runtime/pages/integrations/index.vue +70 -12
- package/dist/runtime/pages/integrations/runs.vue +116 -7
- package/dist/runtime/pages/integrations/secrets.vue +2 -1
- package/dist/runtime/pages/integrations/templates/[slug].vue +2 -1
- package/dist/runtime/pages/integrations/templates/index.vue +18 -15
- package/dist/runtime/pages/integrations/workflows/[id].vue +177 -28
- package/dist/runtime/types/integrations.d.ts +5 -0
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/clipboard.d.ts +33 -0
- package/dist/runtime/utils/clipboard.js +11 -0
- package/dist/runtime/utils/deepClone.d.ts +41 -0
- package/dist/runtime/utils/deepClone.js +3 -0
- package/dist/runtime/utils/formatDuration.d.ts +44 -0
- package/dist/runtime/utils/formatDuration.js +13 -0
- package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
- package/dist/runtime/utils/nodeDisplayName.js +6 -0
- package/dist/runtime/utils/notify.d.ts +1 -1
- package/dist/runtime/utils/runEnvelope.d.ts +31 -0
- package/dist/runtime/utils/runEnvelope.js +12 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
- package/dist/runtime/utils/runFeedQuery.js +66 -0
- package/dist/runtime/utils/runOutputs.d.ts +33 -0
- package/dist/runtime/utils/runOutputs.js +10 -0
- package/dist/runtime/utils/statusStyle.d.ts +24 -0
- package/dist/runtime/utils/statusStyle.js +1 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { findManifest } from "../data/integrationNodeCatalog.js";
|
|
2
|
+
import { resolveDisplayName, resolveLocalized } from "./localize.js";
|
|
3
|
+
export function nodeDisplayName(node) {
|
|
4
|
+
const manifest = findManifest(node.nodeSlug, node.nodeVersion);
|
|
5
|
+
return resolveDisplayName(node.name) || resolveLocalized(manifest?.name) || node.nodeSlug;
|
|
6
|
+
}
|
|
@@ -18,7 +18,6 @@ export declare const toast: ((message: string | (() => string | import("vue").Co
|
|
|
18
18
|
class?: string | undefined;
|
|
19
19
|
id?: number | string | undefined;
|
|
20
20
|
position?: ("top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center") | undefined;
|
|
21
|
-
cancel?: (import("vue-sonner").Action | import("vue").Component) | undefined;
|
|
22
21
|
toasterId?: string | undefined;
|
|
23
22
|
component?: import("vue").Component | undefined;
|
|
24
23
|
componentProps?: any;
|
|
@@ -28,6 +27,7 @@ export declare const toast: ((message: string | (() => string | import("vue").Co
|
|
|
28
27
|
dismissible?: boolean | undefined;
|
|
29
28
|
duration?: number | undefined;
|
|
30
29
|
important?: boolean | undefined;
|
|
30
|
+
cancel?: (import("vue-sonner").Action | import("vue").Component) | undefined;
|
|
31
31
|
onDismiss?: ((toast: import("vue-sonner").ToastT) => void) | undefined;
|
|
32
32
|
onAutoClose?: ((toast: import("vue-sonner").ToastT) => void) | undefined;
|
|
33
33
|
cancelButtonStyle?: import("vue").CSSProperties | undefined;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A run as the API sends it, before anything has been read out of it. */
|
|
2
|
+
export type RunLike = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* The run out of a single-run response, enveloped or not.
|
|
5
|
+
*
|
|
6
|
+
* `GET /workflows/{id}/runs/{runId}` answers the run itself, and the studio's own
|
|
7
|
+
* mock and some callers wrap it in `{ data: … }`. So the envelope has to be
|
|
8
|
+
* optional — but unwrapping on the mere presence of a `data` key is wrong: a run
|
|
9
|
+
* that ever carried one would be unwrapped into it, and the caller would be
|
|
10
|
+
* handed a field instead of a run. Hence the two guards: a body that already
|
|
11
|
+
* looks like a run (`status`, `temporal_run_id`) is the run, whatever else it has
|
|
12
|
+
* on it.
|
|
13
|
+
*
|
|
14
|
+
* This is deliberately generous about the *rest* of the shape. It answers "where
|
|
15
|
+
* is the run in this body", not "is this a usable run" — the caller decides that,
|
|
16
|
+
* because a caller following a run has to be able to count an unusable answer as
|
|
17
|
+
* a failed read rather than sit on it.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The raw platform statuses a run does not come back from.
|
|
21
|
+
*
|
|
22
|
+
* The platform's own words, not the studio's five — the single-run endpoint answers
|
|
23
|
+
* `completed` where a row would read "succeeded" (`utils/statusStyle.ts` maps one to
|
|
24
|
+
* the other), and anything reading this is reading the endpoint.
|
|
25
|
+
*/
|
|
26
|
+
export declare const RUN_TERMINAL: Set<string>;
|
|
27
|
+
/** Whether this run has ended, as the platform reports it. */
|
|
28
|
+
export declare function isTerminalRun(run: {
|
|
29
|
+
status?: unknown;
|
|
30
|
+
} | null | undefined): boolean;
|
|
31
|
+
export declare function unwrapRun(res: unknown): RunLike | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const RUN_TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "canceled", "terminated", "timed_out"]);
|
|
2
|
+
export function isTerminalRun(run) {
|
|
3
|
+
return RUN_TERMINAL.has(String(run?.status ?? "").toLowerCase());
|
|
4
|
+
}
|
|
5
|
+
export function unwrapRun(res) {
|
|
6
|
+
if (!res || typeof res !== "object") return null;
|
|
7
|
+
const o = res;
|
|
8
|
+
if ("data" in o && o.data && typeof o.data === "object" && !("status" in o) && !("temporal_run_id" in o)) {
|
|
9
|
+
return o.data;
|
|
10
|
+
}
|
|
11
|
+
return o;
|
|
12
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { RunStatus, TriggerType } from '../types/integrations.js';
|
|
2
|
+
/**
|
|
3
|
+
* The narrowing of the tenant Runs list, translated for the platform.
|
|
4
|
+
*
|
|
5
|
+
* The list narrows on the server now, so the words the studio puts on its filter
|
|
6
|
+
* have to reach an endpoint that speaks a different vocabulary. The studio draws
|
|
7
|
+
* five outcomes; the platform accepts six statuses, two of which the studio folds
|
|
8
|
+
* into one word. Everything here exists to keep that fold from making a filter
|
|
9
|
+
* contradict the column it sits under — or, as happened once, from asking for a
|
|
10
|
+
* word the platform refuses outright.
|
|
11
|
+
*
|
|
12
|
+
* Kept out of the composable so it can be read on its own — the collapse is the
|
|
13
|
+
* one piece of this feature that is wrong in a way nothing on screen would show.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Every status the platform ACCEPTS in a `status` filter: the six cases of
|
|
17
|
+
* `RunStatus` in the integrations service (`app/Enums/RunStatus.php`), and
|
|
18
|
+
* nothing else.
|
|
19
|
+
*
|
|
20
|
+
* Copied from the enum, not from prose about it. It was written from a comment
|
|
21
|
+
* once and carried a seventh value, `timed_out`, which that enum does not have —
|
|
22
|
+
* so choosing "failed" sent `failed,terminated,timed_out` and the platform
|
|
23
|
+
* answered `422 The selected status.2 is invalid`. The filter did not narrow
|
|
24
|
+
* badly; it failed outright.
|
|
25
|
+
*
|
|
26
|
+
* This is deliberately NOT "every status a run can carry". `workflow_runs.status`
|
|
27
|
+
* is an uncast string column and the service's own summary endpoint accounts for
|
|
28
|
+
* values outside the enum, so a row may arrive carrying one — `mapRunStatus`
|
|
29
|
+
* therefore stays tolerant about what it READS. Only what is SENT is bounded by
|
|
30
|
+
* this list.
|
|
31
|
+
*/
|
|
32
|
+
export declare const API_RUN_STATUSES: readonly ["pending", "running", "completed", "failed", "cancelled", "terminated"];
|
|
33
|
+
/**
|
|
34
|
+
* Platform status → the word the studio draws.
|
|
35
|
+
*
|
|
36
|
+
* Tolerant on purpose, and wider than {@link API_RUN_STATUSES}: the status column
|
|
37
|
+
* is an uncast string on the platform's side, so a row can arrive carrying a word
|
|
38
|
+
* the enum does not list. `terminated` and `timed_out` are failures to an
|
|
39
|
+
* operator and the column says so; anything else fails closed for the reason it
|
|
40
|
+
* does everywhere else — a run the platform cannot classify must never read as a
|
|
41
|
+
* stuck `running`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mapRunStatus(status: string): RunStatus;
|
|
44
|
+
/**
|
|
45
|
+
* The platform statuses behind the studio's words.
|
|
46
|
+
*
|
|
47
|
+
* DERIVED from `mapRunStatus` rather than written out beside it. Restating the
|
|
48
|
+
* collapse would leave two lists to keep in step, and the failure is silent: the
|
|
49
|
+
* filter would come back short a few rows the column beside it still calls
|
|
50
|
+
* failed, which reads as runs having disappeared rather than as a narrowing that
|
|
51
|
+
* is narrower than it says.
|
|
52
|
+
*/
|
|
53
|
+
export declare function apiStatusesFor(words: readonly RunStatus[]): string[];
|
|
54
|
+
/**
|
|
55
|
+
* The orders this studio can ask `GET /runs` for.
|
|
56
|
+
*
|
|
57
|
+
* Three of the four `RunSort` names the service accepts. Two absences are the
|
|
58
|
+
* platform's: ordering by the workflow's name would need a join on the paging
|
|
59
|
+
* path, and the trigger kind has four values, so it groups the list rather than
|
|
60
|
+
* ordering it — the filter beside it answers that question better.
|
|
61
|
+
*
|
|
62
|
+
* The third absence is the STUDIO'S, and it is the reason this list is not
|
|
63
|
+
* simply a mirror of the service's enum. `sort=status` orders by the word the
|
|
64
|
+
* platform stores — `cancelled`, `completed`, `failed`, `pending`, `running`,
|
|
65
|
+
* `terminated` — and those are not the words on the rows: the studio draws its
|
|
66
|
+
* own five, translated, and folds three of the platform's into one of them. A
|
|
67
|
+
* header reading "Status ↑" over rows ordered by a vocabulary the reader never
|
|
68
|
+
* sees is a control that cannot do what it says, and in German it would not even
|
|
69
|
+
* be alphabetical. Ordering by outcome from here needs the platform to order by
|
|
70
|
+
* something the studio can name, which it cannot: the translation lives in this
|
|
71
|
+
* repository and the ordering happens in the other one.
|
|
72
|
+
*/
|
|
73
|
+
export declare const RUN_SORTS: readonly ["created_at", "started_at", "duration"];
|
|
74
|
+
export type RunSort = (typeof RUN_SORTS)[number];
|
|
75
|
+
export type RunSortDirection = 'asc' | 'desc';
|
|
76
|
+
/** The order the list opens in, and the one the platform falls back to. */
|
|
77
|
+
export declare const DEFAULT_RUN_SORT: RunSort;
|
|
78
|
+
export declare const DEFAULT_RUN_DIRECTION: RunSortDirection;
|
|
79
|
+
/** What the Runs list is currently asking the platform for. */
|
|
80
|
+
export interface RunFeedQuery {
|
|
81
|
+
/** 1-based, as the platform pages. */
|
|
82
|
+
page: number;
|
|
83
|
+
perPage: number;
|
|
84
|
+
/** The studio's own outcome words; empty narrows by no outcome. */
|
|
85
|
+
status: RunStatus[];
|
|
86
|
+
/** Empty narrows by no trigger kind. */
|
|
87
|
+
triggerType: TriggerType | '';
|
|
88
|
+
/** Matched against the workflow's name by the platform, not by the browser. */
|
|
89
|
+
search: string;
|
|
90
|
+
/** Which order the page is read in. Not a narrowing — see below. */
|
|
91
|
+
sort: RunSort;
|
|
92
|
+
direction: RunSortDirection;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The query string for `GET /runs` — and for `GET /runs/summary`, which narrows
|
|
96
|
+
* through the same rules and is where a numbered pager gets its total. Both are
|
|
97
|
+
* built from one place so the count can never describe a different set of rows
|
|
98
|
+
* from the page beside it.
|
|
99
|
+
*
|
|
100
|
+
* A filter nobody set is left out rather than sent empty: the platform reads an
|
|
101
|
+
* empty `status` as a value and answers 422.
|
|
102
|
+
*/
|
|
103
|
+
export declare function runFeedQueryString(query: RunFeedQuery): string;
|
|
104
|
+
/**
|
|
105
|
+
* The narrowing alone, without the paging — what `GET /runs/summary` is read
|
|
106
|
+
* with so the pager's total describes the same rows as the page beside it.
|
|
107
|
+
*
|
|
108
|
+
* Empty when nothing narrows, and callers lean on that: the tenant-wide figures
|
|
109
|
+
* the dashboard shows come from the same summary state, so an unnarrowed load
|
|
110
|
+
* may refresh them and a narrowed one may not.
|
|
111
|
+
*
|
|
112
|
+
* The ORDER is deliberately absent. The summary counts what matches; a count has
|
|
113
|
+
* no order, and the endpoint ignores one — sending it would advertise an effect
|
|
114
|
+
* it does not have, and would make an ordered list look like it had asked for a
|
|
115
|
+
* differently counted total.
|
|
116
|
+
*/
|
|
117
|
+
export declare function runFeedNarrowingString(query: RunFeedQuery): string;
|
|
118
|
+
/**
|
|
119
|
+
* Apply a change to the query, returning to the first page whenever the change
|
|
120
|
+
* is a narrowing rather than a page turn.
|
|
121
|
+
*
|
|
122
|
+
* The reset is the point. Page 4 of the old result set is very often past the
|
|
123
|
+
* end of the narrowed one, and the page that comes back is then empty — which
|
|
124
|
+
* an operator reads as "nothing matches" when the truth is "nothing matches
|
|
125
|
+
* this far in". The page size counts as a narrowing here even though it narrows
|
|
126
|
+
* nothing, because it changes what page 4 refers to.
|
|
127
|
+
*/
|
|
128
|
+
export declare function withRunFeedPatch(query: RunFeedQuery, patch: Partial<RunFeedQuery>): RunFeedQuery;
|
|
129
|
+
/**
|
|
130
|
+
* How many pages a total spreads over, for the pager's "page 2 of 7".
|
|
131
|
+
*
|
|
132
|
+
* Never zero: an empty list is still page 1 of 1, and a pager reading "page 1 of
|
|
133
|
+
* 0" is a sentence about nothing.
|
|
134
|
+
*/
|
|
135
|
+
export declare function runFeedLastPage(total: number, perPage: number): number;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const API_RUN_STATUSES = [
|
|
2
|
+
"pending",
|
|
3
|
+
"running",
|
|
4
|
+
"completed",
|
|
5
|
+
"failed",
|
|
6
|
+
"cancelled",
|
|
7
|
+
"terminated"
|
|
8
|
+
];
|
|
9
|
+
export function mapRunStatus(status) {
|
|
10
|
+
switch (status) {
|
|
11
|
+
case "completed":
|
|
12
|
+
return "succeeded";
|
|
13
|
+
case "pending":
|
|
14
|
+
return "queued";
|
|
15
|
+
case "terminated":
|
|
16
|
+
return "failed";
|
|
17
|
+
case "timed_out":
|
|
18
|
+
return "failed";
|
|
19
|
+
case "running":
|
|
20
|
+
return "running";
|
|
21
|
+
case "failed":
|
|
22
|
+
return "failed";
|
|
23
|
+
case "cancelled":
|
|
24
|
+
return "cancelled";
|
|
25
|
+
default:
|
|
26
|
+
return "failed";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function apiStatusesFor(words) {
|
|
30
|
+
if (!words.length) return [];
|
|
31
|
+
return API_RUN_STATUSES.filter((status) => words.includes(mapRunStatus(status)));
|
|
32
|
+
}
|
|
33
|
+
export const RUN_SORTS = ["created_at", "started_at", "duration"];
|
|
34
|
+
export const DEFAULT_RUN_SORT = "created_at";
|
|
35
|
+
export const DEFAULT_RUN_DIRECTION = "desc";
|
|
36
|
+
export function runFeedQueryString(query) {
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
params.set("page", String(query.page));
|
|
39
|
+
params.set("per_page", String(query.perPage));
|
|
40
|
+
for (const [key, value] of narrowingParams(query)) params.set(key, value);
|
|
41
|
+
if (query.sort !== DEFAULT_RUN_SORT || query.direction !== DEFAULT_RUN_DIRECTION) {
|
|
42
|
+
params.set("sort", query.sort);
|
|
43
|
+
params.set("direction", query.direction);
|
|
44
|
+
}
|
|
45
|
+
return params.toString();
|
|
46
|
+
}
|
|
47
|
+
export function runFeedNarrowingString(query) {
|
|
48
|
+
return narrowingParams(query).toString();
|
|
49
|
+
}
|
|
50
|
+
function narrowingParams(query) {
|
|
51
|
+
const params = new URLSearchParams();
|
|
52
|
+
const statuses = apiStatusesFor(query.status);
|
|
53
|
+
if (statuses.length) params.set("status", statuses.join(","));
|
|
54
|
+
if (query.triggerType) params.set("trigger_type", query.triggerType);
|
|
55
|
+
const search = query.search.trim();
|
|
56
|
+
if (search) params.set("search", search);
|
|
57
|
+
return params;
|
|
58
|
+
}
|
|
59
|
+
export function withRunFeedPatch(query, patch) {
|
|
60
|
+
const narrowed = "status" in patch || "triggerType" in patch || "search" in patch || "perPage" in patch || "sort" in patch || "direction" in patch;
|
|
61
|
+
return { ...query, ...patch, ...narrowed ? { page: 1 } : {} };
|
|
62
|
+
}
|
|
63
|
+
export function runFeedLastPage(total, perPage) {
|
|
64
|
+
if (perPage <= 0) return 1;
|
|
65
|
+
return Math.max(1, Math.ceil(total / perPage));
|
|
66
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NameableNode } from './nodeDisplayName.js';
|
|
2
|
+
/** One node's share of what a run returned. */
|
|
3
|
+
export interface RunOutput {
|
|
4
|
+
/** The blob key the value stood under. Kept for addressing, never shown alone. */
|
|
5
|
+
nodeId: string;
|
|
6
|
+
/** What that node is called on the canvas. */
|
|
7
|
+
name: string;
|
|
8
|
+
value: unknown;
|
|
9
|
+
}
|
|
10
|
+
/** A node as the blob carries it — an id, and enough to name it by. */
|
|
11
|
+
export type OutputNode = NameableNode & {
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* What a run returned, split per node and named the way the canvas names it.
|
|
16
|
+
*
|
|
17
|
+
* A run's result is a map keyed by node id — `{"n-1787908672612": {…}}` — which
|
|
18
|
+
* is the worker's addressing and not a thing to show a person. Split against the
|
|
19
|
+
* workflow's own nodes it becomes what the author was actually looking at: one
|
|
20
|
+
* block per node, under the name on the card.
|
|
21
|
+
*
|
|
22
|
+
* `null` means "this is not that shape", and the caller shows the value whole
|
|
23
|
+
* instead of inventing structure for it. That covers three real cases: a result
|
|
24
|
+
* that is a bare value rather than a map, one keyed by something other than node
|
|
25
|
+
* ids, and a run of a workflow whose nodes have since changed — where guessing
|
|
26
|
+
* would put a value under the wrong name, which is worse than not naming it.
|
|
27
|
+
* An empty result is `null` too: there is nothing to split, and the caller has a
|
|
28
|
+
* better sentence for it than an empty list.
|
|
29
|
+
*
|
|
30
|
+
* Order follows the blob rather than the result's own keys, so two runs of one
|
|
31
|
+
* workflow read the same way down the page.
|
|
32
|
+
*/
|
|
33
|
+
export declare function splitRunOutputs(result: unknown, nodes: readonly OutputNode[]): RunOutput[] | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { nodeDisplayName } from "./nodeDisplayName.js";
|
|
2
|
+
export function splitRunOutputs(result, nodes) {
|
|
3
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return null;
|
|
4
|
+
const keys = Object.keys(result);
|
|
5
|
+
if (!keys.length) return null;
|
|
6
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
7
|
+
if (!keys.every((k) => byId.has(k))) return null;
|
|
8
|
+
const values = result;
|
|
9
|
+
return nodes.filter((n) => Object.hasOwn(values, n.id)).map((n) => ({ nodeId: n.id, name: nodeDisplayName(n), value: values[n.id] }));
|
|
10
|
+
}
|
|
@@ -23,6 +23,30 @@ export declare function toneClass(tone: StatusTone): string;
|
|
|
23
23
|
* order. It used to keep its own copy of the list, so a sixth word added here
|
|
24
24
|
* would have been labelled everywhere and silently missing from that menu. */
|
|
25
25
|
export declare const RUN_STATUS_WORDS: readonly ["running", "succeeded", "failed", "cancelled", "queued"];
|
|
26
|
+
/**
|
|
27
|
+
* How wide a list draws its Status column, as a grid track.
|
|
28
|
+
*
|
|
29
|
+
* A fixed width, and it has to be one: the shared table gives the header row, every
|
|
30
|
+
* body row and every skeleton row a grid of their own, so a content-sized track
|
|
31
|
+
* (`max-content`, `auto`, `fit-content`) is resolved per row and a row holding a
|
|
32
|
+
* longer word would step out of line with the header and with the rows around it.
|
|
33
|
+
* The `id` column below says the same thing, and this constant was `minmax(136px,
|
|
34
|
+
* max-content)` for one commit because that warning was read too late.
|
|
35
|
+
*
|
|
36
|
+
* So the number is the promise, and it is a floor rather than a measurement. The
|
|
37
|
+
* column stood at 100px, which fitted every English word with room to spare and let
|
|
38
|
+
* the German "fehlgeschlagen" — 126px as a rendered badge on the machine this was
|
|
39
|
+
* measured on — run out of its cell and under the workflow name beside it (PO-362).
|
|
40
|
+
* 160px is that word plus a quarter, because the rendered width is not ours to know:
|
|
41
|
+
* a font that failed to load, a browser zoom or a reader's own minimum font size all
|
|
42
|
+
* move it, and one of those is why a first attempt at 136 was still reported cut.
|
|
43
|
+
*
|
|
44
|
+
* What happens when it is still not enough, and why that is survivable: the badge
|
|
45
|
+
* carries `max-w-full`, so it is clipped inside its own cell rather than drawn over
|
|
46
|
+
* the column beside it, and the whole word stays readable in its `title`. Raising
|
|
47
|
+
* this number is the fix at that point — not making the track elastic.
|
|
48
|
+
*/
|
|
49
|
+
export declare const RUN_STATUS_COLUMN_WIDTH = "160px";
|
|
26
50
|
/**
|
|
27
51
|
* Raw API/Temporal run vocabulary → the cockpit's run-status labels, so a run
|
|
28
52
|
* never reads "completed" in one surface and "succeeded" in another. This IS
|
|
@@ -77,6 +77,7 @@ export function toneClass(tone) {
|
|
|
77
77
|
return TONE_CLASS[tone];
|
|
78
78
|
}
|
|
79
79
|
export const RUN_STATUS_WORDS = ["running", "succeeded", "failed", "cancelled", "queued"];
|
|
80
|
+
export const RUN_STATUS_COLUMN_WIDTH = "160px";
|
|
80
81
|
const RUN_STATUS_WORD_SET = new Set(RUN_STATUS_WORDS);
|
|
81
82
|
export function normalisedRunStatus(status) {
|
|
82
83
|
if (!status) return "";
|
|
@@ -38,8 +38,10 @@ export interface FindingNodeGroup {
|
|
|
38
38
|
/** The node's human label; `null` when `nodeId` is. */
|
|
39
39
|
label: string | null;
|
|
40
40
|
/**
|
|
41
|
-
* One compact entry per finding, in order and de-duplicated
|
|
42
|
-
*
|
|
41
|
+
* One compact entry per finding, in order and de-duplicated by config key: the
|
|
42
|
+
* setting's name as `fieldLabelOf` gives it — the manifest's label for the banner,
|
|
43
|
+
* the bare key where a caller asks for that — falling back to the message when the
|
|
44
|
+
* path carries no config key at all.
|
|
43
45
|
* **Empty when `nodeId` is `null`** — read `messages` instead.
|
|
44
46
|
*/
|
|
45
47
|
fields: string[];
|
|
@@ -53,6 +55,27 @@ export interface FindingNodeGroup {
|
|
|
53
55
|
* two of them prints `revenexx:business-central-create-customer` at the user.
|
|
54
56
|
*/
|
|
55
57
|
export declare function nodeDisplayLabel(node: WorkflowNode): string;
|
|
58
|
+
/**
|
|
59
|
+
* What a setting is called, for somebody reading *about* it.
|
|
60
|
+
*
|
|
61
|
+
* The same lookup {@link nodeDisplayLabel} does for the node, one level down: the
|
|
62
|
+
* manifest carries a bilingual `label` per config field, and the node's own settings
|
|
63
|
+
* form already renders it. The banner did not ask, so it printed the blob path's last
|
|
64
|
+
* segment — `companyId` beside a form that says **Firma**, in an otherwise German
|
|
65
|
+
* screen, for the same field (PO-401).
|
|
66
|
+
*
|
|
67
|
+
* **The key is the fallback and never the wrong answer.** A workflow can outlive the
|
|
68
|
+
* manifest version it was saved against, and a package can be gone entirely; then there
|
|
69
|
+
* is no label to resolve, and the raw key is worse to read but still names the setting
|
|
70
|
+
* the operator has to find. Falling back to the finding's *message* — what this module
|
|
71
|
+
* does for a finding with no field key at all — was refused here: it turns a short list
|
|
72
|
+
* of names into a list of sentences exactly when the banner is already hardest to read.
|
|
73
|
+
*
|
|
74
|
+
* **For reading about, not for finding by.** `findingsByNodeField` deliberately does NOT
|
|
75
|
+
* use this. Its keys are how the configuration dialog attaches a message to an input, so
|
|
76
|
+
* a translated one would stop matching the field it belongs to.
|
|
77
|
+
*/
|
|
78
|
+
export declare function fieldDisplayLabel(node: WorkflowNode, fieldKey: string): string;
|
|
56
79
|
/**
|
|
57
80
|
* The setting a finding is about: the config key its path ends in.
|
|
58
81
|
*
|
|
@@ -71,7 +94,7 @@ export declare function findingFieldKey(path: string): string | null;
|
|
|
71
94
|
* `null` nodeId), rendered after the node groups — it has no node to hide
|
|
72
95
|
* behind, and dropping it would lose an issue the user has to fix.
|
|
73
96
|
*/
|
|
74
|
-
export declare function groupFindingsByNode(findings: readonly ValidationFinding[], nodes: readonly WorkflowNode[], labelOf?: (node: WorkflowNode) => string): FindingNodeGroup[];
|
|
97
|
+
export declare function groupFindingsByNode(findings: readonly ValidationFinding[], nodes: readonly WorkflowNode[], labelOf?: (node: WorkflowNode) => string, fieldLabelOf?: (node: WorkflowNode, fieldKey: string) => string): FindingNodeGroup[];
|
|
75
98
|
/**
|
|
76
99
|
* Node id → config key → the message to show AT that setting
|
|
77
100
|
* (specs/node-configuration.md AC-8).
|
|
@@ -5,6 +5,11 @@ export function nodeDisplayLabel(node) {
|
|
|
5
5
|
const manifest = findManifest(node.nodeSlug, node.nodeVersion);
|
|
6
6
|
return resolveLocalized(node.name) || resolveLocalized(manifest?.name) || node.nodeSlug;
|
|
7
7
|
}
|
|
8
|
+
export function fieldDisplayLabel(node, fieldKey) {
|
|
9
|
+
const manifest = findManifest(node.nodeSlug, node.nodeVersion);
|
|
10
|
+
const field = manifest?.config?.find((f) => f.key === fieldKey);
|
|
11
|
+
return resolveLocalized(field?.label) || fieldKey;
|
|
12
|
+
}
|
|
8
13
|
export function findingFieldKey(path) {
|
|
9
14
|
const m = /^blob\.nodes\.\d+\.config\.([^.[]+)/.exec(path);
|
|
10
15
|
return m ? m[1] : null;
|
|
@@ -13,10 +18,11 @@ function findingNode(path, nodes) {
|
|
|
13
18
|
const m = /^blob\.nodes\.(\d+)\b/.exec(path);
|
|
14
19
|
return m ? nodes[Number(m[1])] : void 0;
|
|
15
20
|
}
|
|
16
|
-
export function groupFindingsByNode(findings, nodes, labelOf = nodeDisplayLabel) {
|
|
21
|
+
export function groupFindingsByNode(findings, nodes, labelOf = nodeDisplayLabel, fieldLabelOf = fieldDisplayLabel) {
|
|
17
22
|
const nodeGroups = [];
|
|
18
23
|
const orphans = [];
|
|
19
24
|
const byNodeId = /* @__PURE__ */ new Map();
|
|
25
|
+
const seenKeys = /* @__PURE__ */ new Map();
|
|
20
26
|
for (const finding of findings) {
|
|
21
27
|
const node = findingNode(finding.path, nodes);
|
|
22
28
|
if (!node) {
|
|
@@ -29,8 +35,17 @@ export function groupFindingsByNode(findings, nodes, labelOf = nodeDisplayLabel)
|
|
|
29
35
|
byNodeId.set(node.id, group);
|
|
30
36
|
nodeGroups.push(group);
|
|
31
37
|
}
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
38
|
+
const key = findingFieldKey(finding.path);
|
|
39
|
+
if (key === null) {
|
|
40
|
+
if (!group.fields.includes(finding.message)) group.fields.push(finding.message);
|
|
41
|
+
} else {
|
|
42
|
+
const seen = seenKeys.get(node.id) ?? /* @__PURE__ */ new Set();
|
|
43
|
+
seenKeys.set(node.id, seen);
|
|
44
|
+
if (!seen.has(key)) {
|
|
45
|
+
seen.add(key);
|
|
46
|
+
group.fields.push(fieldLabelOf(node, key));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
group.messages.push(finding.message);
|
|
35
50
|
}
|
|
36
51
|
return [...nodeGroups, ...orphans];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-integrations",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
|
|
6
6
|
"repository": {
|