@revenexx/studio-integrations 3.3.0 → 3.4.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 +10 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/RunsTable.vue +14 -8
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +9 -0
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -2
- package/dist/runtime/composables/useIntegrationsRuns.js +12 -39
- package/dist/runtime/pages/integrations/workflows/[id].vue +15 -1
- package/dist/runtime/types/integrations.d.ts +6 -1
- package/dist/runtime/utils/runFeedQuery.d.ts +8 -5
- package/dist/runtime/utils/runFeedQuery.js +9 -4
- package/dist/runtime/utils/runRecord.d.ts +39 -0
- package/dist/runtime/utils/runRecord.js +39 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @revenexx/studio-integrations
|
|
2
2
|
|
|
3
|
+
## 3.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e755223: A workflow's own Runs tab can be narrowed by outcome and by what started a run, with the same Filter control as the Runs page. The platform narrows the workflow's whole history before it pages it, so a failure from three pages back is found, and the pager counts the matches. A narrowing that matches nothing says so, instead of saying the workflow has never run.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 57b4430: A workflow's own Runs tab says what started each run. Every row there read "Manual", whatever had fired the run, because the list sent no trigger and the studio filled the gap with that word. The tab now shows the trigger the platform reports, and a dash where it reports none — a deleted trigger, or a run older than the platform recording it — on the tenant-wide Runs page as well. A run that is still going keeps its trigger while the list re-reads it.
|
|
12
|
+
|
|
3
13
|
## 3.3.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/dist/module.json
CHANGED
|
@@ -39,6 +39,14 @@ type __VLS_Props = {
|
|
|
39
39
|
/** The order the platform is reading the list in. Server mode or orderable only. */
|
|
40
40
|
sort?: RunSort;
|
|
41
41
|
direction?: RunSortDirection;
|
|
42
|
+
/**
|
|
43
|
+
* The owning list asks the platform to narrow it even though the table is not
|
|
44
|
+
* in server mode — the workflow's own Runs tab, which narrows on the server but
|
|
45
|
+
* keeps its own pager (PO-319). The table then offers the same narrowing control
|
|
46
|
+
* as the tenant-wide list and reports a choice through `narrow`. Server mode
|
|
47
|
+
* implies it.
|
|
48
|
+
*/
|
|
49
|
+
narrowable?: boolean;
|
|
42
50
|
};
|
|
43
51
|
type __VLS_PublicProps = __VLS_Props & {
|
|
44
52
|
/** 1-based, as the platform pages. The shared table counts from zero. */
|
|
@@ -81,5 +89,6 @@ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {},
|
|
|
81
89
|
showWorkflow: boolean;
|
|
82
90
|
serverMode: boolean;
|
|
83
91
|
orderable: boolean;
|
|
92
|
+
narrowable: boolean;
|
|
84
93
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
85
94
|
export default _default;
|
|
@@ -27,13 +27,14 @@ const props = defineProps({
|
|
|
27
27
|
triggerType: { type: String, required: false, default: "" },
|
|
28
28
|
orderable: { type: Boolean, required: false, default: false },
|
|
29
29
|
sort: { type: null, required: false, default: DEFAULT_RUN_SORT },
|
|
30
|
-
direction: { type: String, required: false, default: DEFAULT_RUN_DIRECTION }
|
|
30
|
+
direction: { type: String, required: false, default: DEFAULT_RUN_DIRECTION },
|
|
31
|
+
narrowable: { type: Boolean, required: false, default: false }
|
|
31
32
|
});
|
|
32
33
|
const emit = defineEmits(["changed", "narrow", "search", "page-size", "reorder"]);
|
|
33
34
|
const page = defineModel("page", { type: Number, ...{ default: 1 } });
|
|
34
35
|
const t = useIntegrationsT();
|
|
35
36
|
const router = useRouter();
|
|
36
|
-
const emptyText = computed(() =>
|
|
37
|
+
const emptyText = computed(() => anyNarrowing.value || searchText.value ? t("integration.runs.table.empty.narrowed") : props.emptyText ?? t("integration.runs.table.empty"));
|
|
37
38
|
const now = useNow();
|
|
38
39
|
function openRun(r) {
|
|
39
40
|
void router.push({ path: `/integrations/workflows/${r.workflowId}`, query: { run: r.id } });
|
|
@@ -45,7 +46,8 @@ function liveDuration(run) {
|
|
|
45
46
|
const end = run.finishedAt ? Date.parse(run.finishedAt) : now.value;
|
|
46
47
|
return end - start;
|
|
47
48
|
}
|
|
48
|
-
const
|
|
49
|
+
const narrowsOnServer = computed(() => props.serverMode || props.narrowable);
|
|
50
|
+
const filterTabs = computed(() => props.pagination && !narrowsOnServer.value ? [
|
|
49
51
|
{
|
|
50
52
|
key: "status",
|
|
51
53
|
label: t("integration.runs.table.column.status"),
|
|
@@ -140,7 +142,7 @@ const columns = computed(() => [
|
|
|
140
142
|
:row-key="(r) => r.id"
|
|
141
143
|
:search="{ placeholder: t('integration.runs.table.search.placeholder') }"
|
|
142
144
|
:filter-tabs="filterTabs"
|
|
143
|
-
:filter="
|
|
145
|
+
:filter="narrowsOnServer ? { width: 'w-72', active: anyNarrowing } : false"
|
|
144
146
|
:server-mode="serverMode"
|
|
145
147
|
:total="total"
|
|
146
148
|
:loading="loading"
|
|
@@ -160,14 +162,15 @@ const columns = computed(() => [
|
|
|
160
162
|
apply them together; this one picks one value per dimension and applies it on
|
|
161
163
|
the spot, because each pick is a request to the platform and staging them
|
|
162
164
|
would only delay the same round trip. So the shared narrowing helper does not
|
|
163
|
-
reach
|
|
164
|
-
rather than left to be discovered
|
|
165
|
+
reach either run list, which share one of their own in `e2e/support/runFilter.ts`
|
|
166
|
+
— said in both places rather than left to be discovered
|
|
167
|
+
(`e2e/support/studio-table.ts`).
|
|
165
168
|
|
|
166
169
|
What is still NOT copied is the count beside each option: the built-in counts
|
|
167
170
|
the rows in hand, and counting a page here would put a number next to
|
|
168
171
|
"failed" that describes this page rather than the history the option narrows.
|
|
169
172
|
-->
|
|
170
|
-
<template v-if="
|
|
173
|
+
<template v-if="narrowsOnServer" #filter-menu>
|
|
171
174
|
<!--
|
|
172
175
|
Two selects rather than the sub-menus this slot used to hold. Since
|
|
173
176
|
`studio-shared` 0.3.2 the filter trigger opens a popover instead of a
|
|
@@ -242,9 +245,12 @@ const columns = computed(() => [
|
|
|
242
245
|
</template>
|
|
243
246
|
|
|
244
247
|
<template #cell-trigger="{ row: run }">
|
|
245
|
-
<span class="inline-flex items-center whitespace-nowrap rounded-md border border-black/[0.06] bg-muted/50 px-1.5 py-0.5 text-xs font-medium uppercase text-muted-foreground">
|
|
248
|
+
<span v-if="run.triggerType" class="inline-flex items-center whitespace-nowrap rounded-md border border-black/[0.06] bg-muted/50 px-1.5 py-0.5 text-xs font-medium uppercase text-muted-foreground">
|
|
246
249
|
{{ triggerTypeLabel(run.triggerType) }}
|
|
247
250
|
</span>
|
|
251
|
+
<!-- What started the run is not known: nothing is said, rather than a guess
|
|
252
|
+
(specs/run-row.md AC-14). -->
|
|
253
|
+
<span v-else class="text-xs text-muted-foreground">—</span>
|
|
248
254
|
</template>
|
|
249
255
|
|
|
250
256
|
<template #cell-started="{ row: run }">
|
|
@@ -39,6 +39,14 @@ type __VLS_Props = {
|
|
|
39
39
|
/** The order the platform is reading the list in. Server mode or orderable only. */
|
|
40
40
|
sort?: RunSort;
|
|
41
41
|
direction?: RunSortDirection;
|
|
42
|
+
/**
|
|
43
|
+
* The owning list asks the platform to narrow it even though the table is not
|
|
44
|
+
* in server mode — the workflow's own Runs tab, which narrows on the server but
|
|
45
|
+
* keeps its own pager (PO-319). The table then offers the same narrowing control
|
|
46
|
+
* as the tenant-wide list and reports a choice through `narrow`. Server mode
|
|
47
|
+
* implies it.
|
|
48
|
+
*/
|
|
49
|
+
narrowable?: boolean;
|
|
42
50
|
};
|
|
43
51
|
type __VLS_PublicProps = __VLS_Props & {
|
|
44
52
|
/** 1-based, as the platform pages. The shared table counts from zero. */
|
|
@@ -81,5 +89,6 @@ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {},
|
|
|
81
89
|
showWorkflow: boolean;
|
|
82
90
|
serverMode: boolean;
|
|
83
91
|
orderable: boolean;
|
|
92
|
+
narrowable: boolean;
|
|
84
93
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
85
94
|
export default _default;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RunStatus, RunStepRow, WorkflowRun } from '../types/integrations.js';
|
|
2
|
-
import type { RunFeedQuery, RunSort, RunSortDirection } from '../utils/runFeedQuery.js';
|
|
2
|
+
import type { RunFeedQuery, RunSort, RunSortDirection, WorkflowRunsNarrowing } from '../utils/runFeedQuery.js';
|
|
3
3
|
export type { RunFeedQuery };
|
|
4
4
|
interface RunSummary {
|
|
5
5
|
total: number;
|
|
@@ -27,6 +27,8 @@ export declare function useIntegrationsRuns(): {
|
|
|
27
27
|
perPage?: number;
|
|
28
28
|
sort?: RunSort;
|
|
29
29
|
direction?: RunSortDirection;
|
|
30
|
+
/** Narrowed by the platform, before it pages — so `total` counts what is left. */
|
|
31
|
+
narrowing?: WorkflowRunsNarrowing;
|
|
30
32
|
}) => Promise<{
|
|
31
33
|
runs: WorkflowRun[];
|
|
32
34
|
total: number;
|
|
@@ -38,7 +40,7 @@ export declare function useIntegrationsRuns(): {
|
|
|
38
40
|
status?: RunStatus;
|
|
39
41
|
}) => WorkflowRun[];
|
|
40
42
|
get: (id: string) => WorkflowRun | null;
|
|
41
|
-
fetchRun: (workflowId: number | string, runId: number | string, workflowName: string) => Promise<WorkflowRun>;
|
|
43
|
+
fetchRun: (workflowId: number | string, runId: number | string, workflowName: string, listed?: WorkflowRun) => Promise<WorkflowRun>;
|
|
42
44
|
latestPerWorkflow: any;
|
|
43
45
|
latestLoaded: any;
|
|
44
46
|
latestLoading: any;
|
|
@@ -4,6 +4,7 @@ import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
|
4
4
|
import { errorReason } from "../utils/apiError.js";
|
|
5
5
|
import { bareRecord } from "../utils/apiEnvelope.js";
|
|
6
6
|
import { RUN_OWN_FIELDS } from "../utils/runEnvelope.js";
|
|
7
|
+
import { keepListTrigger, mapRun } from "../utils/runRecord.js";
|
|
7
8
|
import { useLoadGate } from "./useLoadGate.js";
|
|
8
9
|
import {
|
|
9
10
|
DEFAULT_RUN_DIRECTION,
|
|
@@ -42,39 +43,6 @@ export function useIntegrationsRuns() {
|
|
|
42
43
|
const SYNC_LIMIT = 5;
|
|
43
44
|
const api = useIntegrationsApi();
|
|
44
45
|
const t = useIntegrationsT();
|
|
45
|
-
function mapRun(r, workflowName = "") {
|
|
46
|
-
const startedMs = r.started_at ? new Date(r.started_at).getTime() : null;
|
|
47
|
-
const finishedMs = r.completed_at ? new Date(r.completed_at).getTime() : null;
|
|
48
|
-
return {
|
|
49
|
-
id: String(r.id),
|
|
50
|
-
workflowId: r.workflow_id,
|
|
51
|
-
workflowName: r.workflow_name ?? workflowName,
|
|
52
|
-
status: mapRunStatus(r.status),
|
|
53
|
-
// Kept beside the mapped word, because the mapping folds `terminated` into
|
|
54
|
-
// `failed` and the platform resumes only the literal `failed` (PO-355).
|
|
55
|
-
apiStatus: r.status,
|
|
56
|
-
temporalRunId: r.temporal_run_id ?? null,
|
|
57
|
-
triggerId: r.trigger_handle ?? "",
|
|
58
|
-
// The platform reports what started the run. This was hardcoded to
|
|
59
|
-
// `manual` because the payload carried nothing, which left the trigger
|
|
60
|
-
// column and its filter with one populated value in production.
|
|
61
|
-
// Null — the trigger was deleted, or the run predates trigger_handle, or the
|
|
62
|
-
// row came from a per-workflow read, which sends no trigger at all — is read
|
|
63
|
-
// as `manual` here although the platform said nothing (IN-9, api-contract
|
|
64
|
-
// runs.feed F6 and runs.execution F2).
|
|
65
|
-
triggerType: r.trigger_type ?? "manual",
|
|
66
|
-
startedAt: r.started_at ?? "",
|
|
67
|
-
finishedAt: r.completed_at ?? null,
|
|
68
|
-
// The list carries the reason a run failed; dropping it here was why every
|
|
69
|
-
// failure read the same wherever the list is shown. An empty string is not
|
|
70
|
-
// a reason — it has to fall through to whatever the surface says instead,
|
|
71
|
-
// or it renders as a blank line where the sentence belongs.
|
|
72
|
-
errorMessage: r.error || void 0,
|
|
73
|
-
// Derived from the timestamps — the list payload carries no duration.
|
|
74
|
-
durationMs: startedMs != null && finishedMs != null && finishedMs >= startedMs ? finishedMs - startedMs : null,
|
|
75
|
-
steps: []
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
46
|
async function load(force = false) {
|
|
79
47
|
if (!await gate.claim(force)) return runs.value;
|
|
80
48
|
error.value = null;
|
|
@@ -103,7 +71,7 @@ export function useIntegrationsRuns() {
|
|
|
103
71
|
const active = runs.value.filter((r) => r.status === "running" || r.status === "queued").sort((a, b) => Number(b.id) - Number(a.id)).slice(0, SYNC_LIMIT);
|
|
104
72
|
await Promise.allSettled(active.map(async (r) => {
|
|
105
73
|
try {
|
|
106
|
-
const fresh = await fetchRun(r.workflowId, r.id, r.workflowName);
|
|
74
|
+
const fresh = await fetchRun(r.workflowId, r.id, r.workflowName, r);
|
|
107
75
|
runs.value = runs.value.map((x) => x.id === fresh.id ? fresh : x);
|
|
108
76
|
} catch {
|
|
109
77
|
}
|
|
@@ -125,22 +93,26 @@ export function useIntegrationsRuns() {
|
|
|
125
93
|
const page = opts.page ?? 1;
|
|
126
94
|
const perPage = opts.perPage ?? 25;
|
|
127
95
|
const res = await api.get(
|
|
128
|
-
`/workflows/${workflowId}/runs?${workflowRunsQueryString(page, perPage, opts.sort, opts.direction)}`
|
|
96
|
+
`/workflows/${workflowId}/runs?${workflowRunsQueryString(page, perPage, opts.sort, opts.direction, opts.narrowing)}`
|
|
129
97
|
);
|
|
130
98
|
let list2 = (res?.data ?? []).map((r) => mapRun(r, workflowName));
|
|
131
99
|
if (opts.reconcile) {
|
|
132
100
|
const active = list2.filter((r) => r.status === "running" || r.status === "queued").sort((a, b) => Number(b.id) - Number(a.id)).slice(0, SYNC_LIMIT);
|
|
133
101
|
await Promise.allSettled(active.map(async (r) => {
|
|
134
102
|
try {
|
|
135
|
-
const fresh = await fetchRun(workflowId, r.id, workflowName);
|
|
103
|
+
const fresh = await fetchRun(workflowId, r.id, workflowName, r);
|
|
136
104
|
list2 = list2.map((x) => x.id === fresh.id ? fresh : x);
|
|
137
105
|
} catch {
|
|
138
106
|
}
|
|
139
107
|
}));
|
|
140
108
|
}
|
|
109
|
+
const wanted = opts.narrowing?.status ?? [];
|
|
110
|
+
const before = list2.length;
|
|
111
|
+
if (wanted.length) list2 = list2.filter((r) => wanted.includes(r.status));
|
|
112
|
+
const total2 = res?.meta?.total ?? before;
|
|
141
113
|
return {
|
|
142
114
|
runs: list2,
|
|
143
|
-
total:
|
|
115
|
+
total: total2 - (before - list2.length),
|
|
144
116
|
page: res?.meta?.current_page ?? page,
|
|
145
117
|
lastPage: res?.meta?.last_page ?? 1
|
|
146
118
|
};
|
|
@@ -202,9 +174,10 @@ export function useIntegrationsRuns() {
|
|
|
202
174
|
const retryRun = (workflowId, runId) => runAction(workflowId, runId, "retry");
|
|
203
175
|
const resumeRun = (workflowId, runId, reason) => runAction(workflowId, runId, "resume", reason);
|
|
204
176
|
const getRun = (workflowId, runId) => api.get(`/workflows/${workflowId}/runs/${runId}`);
|
|
205
|
-
async function fetchRun(workflowId, runId, workflowName) {
|
|
177
|
+
async function fetchRun(workflowId, runId, workflowName, listed) {
|
|
206
178
|
const apiRun = bareRecord(await getRun(workflowId, runId), RUN_OWN_FIELDS);
|
|
207
|
-
|
|
179
|
+
const fresh = mapRun(apiRun, workflowName);
|
|
180
|
+
return listed ? keepListTrigger(fresh, listed) : fresh;
|
|
208
181
|
}
|
|
209
182
|
const getDetails = (workflowId, runId) => api.get(`/workflows/${workflowId}/runs/${runId}/details`);
|
|
210
183
|
const getSteps = (workflowId, runId, includePayloads = false) => api.get(
|
|
@@ -209,6 +209,8 @@ watch(id, async () => {
|
|
|
209
209
|
if (!w) await leaveTo("/integrations/workflows");
|
|
210
210
|
draft.value = null;
|
|
211
211
|
warningsExpanded.value = false;
|
|
212
|
+
runsStatus.value = [];
|
|
213
|
+
runsTrigger.value = "";
|
|
212
214
|
if (tab.value === "runs") void loadRuns(1);
|
|
213
215
|
settleBuildStatus();
|
|
214
216
|
}, { immediate: true });
|
|
@@ -530,6 +532,8 @@ const runsLastPage = ref(1);
|
|
|
530
532
|
const runsWanted = ref(1);
|
|
531
533
|
const runsSort = ref(DEFAULT_RUN_SORT);
|
|
532
534
|
const runsDirection = ref(DEFAULT_RUN_DIRECTION);
|
|
535
|
+
const runsStatus = ref([]);
|
|
536
|
+
const runsTrigger = ref("");
|
|
533
537
|
let runsSeq = 0;
|
|
534
538
|
let runsLoadingSeq = 0;
|
|
535
539
|
const RUNS_POLL_MS = 3e3;
|
|
@@ -634,7 +638,8 @@ async function loadRuns(page = runsWanted.value, opts = {}) {
|
|
|
634
638
|
page,
|
|
635
639
|
perPage: RUNS_PER_PAGE,
|
|
636
640
|
sort: runsSort.value,
|
|
637
|
-
direction: runsDirection.value
|
|
641
|
+
direction: runsDirection.value,
|
|
642
|
+
narrowing: { status: runsStatus.value, triggerType: runsTrigger.value }
|
|
638
643
|
});
|
|
639
644
|
if (seq !== runsSeq) return;
|
|
640
645
|
wfRuns.value = res.runs;
|
|
@@ -655,6 +660,11 @@ function reorderRuns(order) {
|
|
|
655
660
|
runsDirection.value = order.direction;
|
|
656
661
|
void loadRuns(1);
|
|
657
662
|
}
|
|
663
|
+
function narrowRuns(patch) {
|
|
664
|
+
if (patch.status !== void 0) runsStatus.value = patch.status;
|
|
665
|
+
if (patch.triggerType !== void 0) runsTrigger.value = patch.triggerType;
|
|
666
|
+
void loadRuns(1);
|
|
667
|
+
}
|
|
658
668
|
function goRunsPage(p) {
|
|
659
669
|
const next = Math.min(Math.max(1, p), runsLastPage.value);
|
|
660
670
|
if (next !== runsWanted.value) void loadRuns(next);
|
|
@@ -1179,9 +1189,13 @@ async function confirmDeactivate() {
|
|
|
1179
1189
|
orderable
|
|
1180
1190
|
:sort="runsSort"
|
|
1181
1191
|
:direction="runsDirection"
|
|
1192
|
+
narrowable
|
|
1193
|
+
:status="runsStatus"
|
|
1194
|
+
:trigger-type="runsTrigger"
|
|
1182
1195
|
:empty-text="t('integration.editor.runs.empty')"
|
|
1183
1196
|
@changed="() => loadRuns()"
|
|
1184
1197
|
@reorder="reorderRuns"
|
|
1198
|
+
@narrow="narrowRuns"
|
|
1185
1199
|
/>
|
|
1186
1200
|
|
|
1187
1201
|
<!-- Server pagination — the endpoint pages, so the whole history is reachable. -->
|
|
@@ -822,7 +822,12 @@ export interface WorkflowRun {
|
|
|
822
822
|
/** Temporal run id for this execution; null when the row predates Temporal. */
|
|
823
823
|
temporalRunId?: string | null;
|
|
824
824
|
triggerId: string;
|
|
825
|
-
|
|
825
|
+
/**
|
|
826
|
+
* What started the run, as the platform reported it. Null where it reported
|
|
827
|
+
* nothing — the trigger was deleted, or the run predates it being recorded — and
|
|
828
|
+
* drawn as nothing rather than as a guess (PO-335).
|
|
829
|
+
*/
|
|
830
|
+
triggerType: TriggerType | null;
|
|
826
831
|
startedAt: string;
|
|
827
832
|
finishedAt: string | null;
|
|
828
833
|
durationMs: number | null;
|
|
@@ -122,13 +122,16 @@ export interface RunFeedQuery {
|
|
|
122
122
|
* empty `status` as a value and answers 422.
|
|
123
123
|
*/
|
|
124
124
|
export declare function runFeedQueryString(query: RunFeedQuery): string;
|
|
125
|
+
/** The narrowing a workflow's own list takes: the feed's, minus the text search. */
|
|
126
|
+
export type WorkflowRunsNarrowing = Pick<RunFeedQuery, 'status' | 'triggerType'>;
|
|
125
127
|
/**
|
|
126
|
-
* The query string for a workflow's own `GET /workflows/{id}/runs`: a page,
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
128
|
+
* The query string for a workflow's own `GET /workflows/{id}/runs`: a page, the
|
|
129
|
+
* order it is read in, and the narrowing by outcome and trigger kind (PO-319) —
|
|
130
|
+
* each sent by the same rule as the tenant feed's, so the two lists can never ask
|
|
131
|
+
* for one thing two ways. No text search: that endpoint takes none, and the tab's
|
|
132
|
+
* search box searches the page in hand.
|
|
130
133
|
*/
|
|
131
|
-
export declare function workflowRunsQueryString(page: number, perPage: number, sort?: RunSort, direction?: RunSortDirection): string;
|
|
134
|
+
export declare function workflowRunsQueryString(page: number, perPage: number, sort?: RunSort, direction?: RunSortDirection, narrowing?: WorkflowRunsNarrowing): string;
|
|
132
135
|
/**
|
|
133
136
|
* The narrowing alone, without the paging — what `GET /runs/summary` is read
|
|
134
137
|
* with so the pager's total describes the same rows as the page beside it.
|
|
@@ -29,10 +29,11 @@ export function runFeedQueryString(query) {
|
|
|
29
29
|
setOrder(params, query.sort, query.direction);
|
|
30
30
|
return params.toString();
|
|
31
31
|
}
|
|
32
|
-
export function workflowRunsQueryString(page, perPage, sort = DEFAULT_RUN_SORT, direction = DEFAULT_RUN_DIRECTION) {
|
|
32
|
+
export function workflowRunsQueryString(page, perPage, sort = DEFAULT_RUN_SORT, direction = DEFAULT_RUN_DIRECTION, narrowing = { status: [], triggerType: "" }) {
|
|
33
33
|
const params = new URLSearchParams();
|
|
34
34
|
params.set("page", String(page));
|
|
35
35
|
params.set("per_page", String(perPage));
|
|
36
|
+
for (const [key, value] of outcomeParams(narrowing)) params.set(key, value);
|
|
36
37
|
setOrder(params, sort, direction);
|
|
37
38
|
return params.toString();
|
|
38
39
|
}
|
|
@@ -45,11 +46,15 @@ function setOrder(params, sort, direction) {
|
|
|
45
46
|
export function runFeedNarrowingString(query) {
|
|
46
47
|
return narrowingParams(query).toString();
|
|
47
48
|
}
|
|
48
|
-
function
|
|
49
|
+
function outcomeParams(narrowing) {
|
|
49
50
|
const params = new URLSearchParams();
|
|
50
|
-
const statuses = apiStatusesFor(
|
|
51
|
+
const statuses = apiStatusesFor(narrowing.status);
|
|
51
52
|
if (statuses.length) params.set("status", statuses.join(","));
|
|
52
|
-
if (
|
|
53
|
+
if (narrowing.triggerType) params.set("trigger_type", narrowing.triggerType);
|
|
54
|
+
return params;
|
|
55
|
+
}
|
|
56
|
+
function narrowingParams(query) {
|
|
57
|
+
const params = outcomeParams(query);
|
|
53
58
|
const search = query.search.trim();
|
|
54
59
|
if (search) params.set("search", search);
|
|
55
60
|
return params;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { TriggerType, WorkflowRun } from '../types/integrations.js';
|
|
2
|
+
/**
|
|
3
|
+
* A run as either list sends it, and as the by-id read does. The three do not
|
|
4
|
+
* carry the same fields: see `mapRun` and `keepListTrigger` for what fills in.
|
|
5
|
+
*/
|
|
6
|
+
export interface ApiRun {
|
|
7
|
+
id: number;
|
|
8
|
+
workflow_id: number;
|
|
9
|
+
status: string;
|
|
10
|
+
temporal_workflow_id?: string;
|
|
11
|
+
temporal_run_id?: string;
|
|
12
|
+
started_at?: string;
|
|
13
|
+
completed_at?: string;
|
|
14
|
+
/** Why the run failed, as the platform recorded it. Present in the list too. */
|
|
15
|
+
error?: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Present on the tenant feed, absent on the per-workflow subresource — which
|
|
18
|
+
* has no need for them, since the caller supplied the workflow and the tab
|
|
19
|
+
* shows one workflow's runs.
|
|
20
|
+
*/
|
|
21
|
+
workflow_name?: string;
|
|
22
|
+
trigger_type?: TriggerType | null;
|
|
23
|
+
trigger_handle?: string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `workflowName` is a FALLBACK, not the source. The tenant feed carries the
|
|
27
|
+
* name on the row; the per-workflow subresource does not, and its caller knows
|
|
28
|
+
* it. Passing it used to be the only way a row had a name at all.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mapRun(r: ApiRun, workflowName?: string): WorkflowRun;
|
|
31
|
+
/**
|
|
32
|
+
* A run re-read by id, keeping what started it from the row it replaces.
|
|
33
|
+
*
|
|
34
|
+
* The by-id read sends no trigger — only the two lists do — so swapping a
|
|
35
|
+
* reconciled run in whole blanked a schedule's run to no trigger at all, a row
|
|
36
|
+
* after the list had named it (api-contract runs.execution F2). What started a
|
|
37
|
+
* run does not change while it runs, so the list's word stays the right one.
|
|
38
|
+
*/
|
|
39
|
+
export declare function keepListTrigger(fresh: WorkflowRun, listed: WorkflowRun): WorkflowRun;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mapRunStatus } from "./runFeedQuery.js";
|
|
2
|
+
export function mapRun(r, workflowName = "") {
|
|
3
|
+
const startedMs = r.started_at ? new Date(r.started_at).getTime() : null;
|
|
4
|
+
const finishedMs = r.completed_at ? new Date(r.completed_at).getTime() : null;
|
|
5
|
+
return {
|
|
6
|
+
id: String(r.id),
|
|
7
|
+
workflowId: r.workflow_id,
|
|
8
|
+
workflowName: r.workflow_name ?? workflowName,
|
|
9
|
+
status: mapRunStatus(r.status),
|
|
10
|
+
// Kept beside the mapped word, because the mapping folds `terminated` into
|
|
11
|
+
// `failed` and the platform resumes only the literal `failed` (PO-355).
|
|
12
|
+
apiStatus: r.status,
|
|
13
|
+
temporalRunId: r.temporal_run_id ?? null,
|
|
14
|
+
triggerId: r.trigger_handle ?? "",
|
|
15
|
+
// The platform reports what started the run, on both lists. Null — the
|
|
16
|
+
// trigger was deleted, or the run predates trigger_handle — stays null: it
|
|
17
|
+
// was read as `manual` once, and every row of a workflow's own Runs tab then
|
|
18
|
+
// claimed a start by hand, because that list sent no trigger at all (PO-335).
|
|
19
|
+
// A by-id read still sends none; see `keepListTrigger`.
|
|
20
|
+
triggerType: r.trigger_type ?? null,
|
|
21
|
+
startedAt: r.started_at ?? "",
|
|
22
|
+
finishedAt: r.completed_at ?? null,
|
|
23
|
+
// The list carries the reason a run failed; dropping it here was why every
|
|
24
|
+
// failure read the same wherever the list is shown. An empty string is not
|
|
25
|
+
// a reason — it has to fall through to whatever the surface says instead,
|
|
26
|
+
// or it renders as a blank line where the sentence belongs.
|
|
27
|
+
errorMessage: r.error || void 0,
|
|
28
|
+
// Derived from the timestamps — the list payload carries no duration.
|
|
29
|
+
durationMs: startedMs != null && finishedMs != null && finishedMs >= startedMs ? finishedMs - startedMs : null,
|
|
30
|
+
steps: []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function keepListTrigger(fresh, listed) {
|
|
34
|
+
return {
|
|
35
|
+
...fresh,
|
|
36
|
+
triggerId: fresh.triggerId || listed.triggerId,
|
|
37
|
+
triggerType: fresh.triggerType ?? listed.triggerType
|
|
38
|
+
};
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-integrations",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.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": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"eslint": "^10.11.0",
|
|
64
64
|
"nuxt": "^4.2.2",
|
|
65
65
|
"typescript": "5.9.3",
|
|
66
|
-
"vitest": "^
|
|
66
|
+
"vitest": "^5.0.5",
|
|
67
67
|
"vue": "^3.5.26",
|
|
68
68
|
"vue-tsc": "^2.2.0"
|
|
69
69
|
},
|