@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,22 @@
|
|
|
1
|
+
import { Position } from "@vue-flow/core";
|
|
2
|
+
export const SIDE_POSITION = {
|
|
3
|
+
top: Position.Top,
|
|
4
|
+
right: Position.Right,
|
|
5
|
+
bottom: Position.Bottom,
|
|
6
|
+
left: Position.Left
|
|
7
|
+
};
|
|
8
|
+
export const OPPOSITE_SIDE = {
|
|
9
|
+
top: "bottom",
|
|
10
|
+
right: "left",
|
|
11
|
+
bottom: "top",
|
|
12
|
+
left: "right"
|
|
13
|
+
};
|
|
14
|
+
const SIDE_SPREAD_AXIS = {
|
|
15
|
+
top: "left",
|
|
16
|
+
right: "top",
|
|
17
|
+
bottom: "left",
|
|
18
|
+
left: "top"
|
|
19
|
+
};
|
|
20
|
+
export function distributedStyle(side, i, total) {
|
|
21
|
+
return { [SIDE_SPREAD_AXIS[side]]: `${(i + 1) / (total + 1) * 100}%` };
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
2
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
|
-
import {
|
|
3
|
+
import { errorReason } from "../utils/apiError.js";
|
|
4
4
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
5
|
export function useCredentialTypes() {
|
|
6
6
|
const types = useState("integrations.credentialTypes", () => []);
|
|
@@ -16,10 +16,6 @@ export function useCredentialTypes() {
|
|
|
16
16
|
if (Array.isArray(obj?.data)) return obj.data;
|
|
17
17
|
return [];
|
|
18
18
|
}
|
|
19
|
-
function captureError(e, fallback) {
|
|
20
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
21
|
-
return e?.message ?? fallback;
|
|
22
|
-
}
|
|
23
19
|
async function load(force = false) {
|
|
24
20
|
if (!await gate.claim(force)) return types.value;
|
|
25
21
|
error.value = null;
|
|
@@ -27,7 +23,7 @@ export function useCredentialTypes() {
|
|
|
27
23
|
types.value = normalize(await api.get("/credential-types"));
|
|
28
24
|
loaded.value = true;
|
|
29
25
|
} catch (e) {
|
|
30
|
-
error.value =
|
|
26
|
+
error.value = errorReason(e, t("integration.credentials.error.loadTypes"));
|
|
31
27
|
} finally {
|
|
32
28
|
gate.release();
|
|
33
29
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
|
-
import {
|
|
2
|
+
import { errorReason } from "../utils/apiError.js";
|
|
3
3
|
import { useLoadGate } from "./useLoadGate.js";
|
|
4
4
|
export function useIntegrationsAudit() {
|
|
5
5
|
const api = useIntegrationsApi();
|
|
@@ -28,7 +28,7 @@ export function useIntegrationsAudit() {
|
|
|
28
28
|
entries.value = append ? [...entries.value, ...data] : data;
|
|
29
29
|
nextCursor.value = res.meta?.next_cursor ?? null;
|
|
30
30
|
} catch (e) {
|
|
31
|
-
error.value = e
|
|
31
|
+
error.value = errorReason(e, "Could not load the audit log.");
|
|
32
32
|
} finally {
|
|
33
33
|
gate.release();
|
|
34
34
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
|
+
import { IntegrationsApiError, errorReason } from "../utils/apiError.js";
|
|
5
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
6
6
|
export function useIntegrationsCredentials() {
|
|
7
7
|
const credentials = useState("integrations.credentials", () => []);
|
|
@@ -20,10 +20,6 @@ export function useIntegrationsCredentials() {
|
|
|
20
20
|
function unwrap(res) {
|
|
21
21
|
return res?.data ?? res;
|
|
22
22
|
}
|
|
23
|
-
function captureError(e, fallback) {
|
|
24
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
25
|
-
return e?.message ?? fallback;
|
|
26
|
-
}
|
|
27
23
|
async function load(force = false) {
|
|
28
24
|
if (!await gate.claim(force)) return credentials.value;
|
|
29
25
|
error.value = null;
|
|
@@ -31,7 +27,7 @@ export function useIntegrationsCredentials() {
|
|
|
31
27
|
credentials.value = normalize(await api.get("/credentials"));
|
|
32
28
|
loaded.value = true;
|
|
33
29
|
} catch (e) {
|
|
34
|
-
error.value =
|
|
30
|
+
error.value = errorReason(e, t("integration.credentials.error.loadFailed"));
|
|
35
31
|
} finally {
|
|
36
32
|
gate.release();
|
|
37
33
|
}
|
|
@@ -62,7 +58,7 @@ export function useIntegrationsCredentials() {
|
|
|
62
58
|
const res = await api.post("/credentials/test", { credential_type_slug: credentialTypeSlug, config });
|
|
63
59
|
return res?.ok === false ? res : { ok: true, message: res?.message };
|
|
64
60
|
} catch (e) {
|
|
65
|
-
return { ok: false, error:
|
|
61
|
+
return { ok: false, error: errorReason(e, t("integration.credentials.error.testFailed")) };
|
|
66
62
|
}
|
|
67
63
|
}
|
|
68
64
|
async function test(id) {
|
|
@@ -70,7 +66,7 @@ export function useIntegrationsCredentials() {
|
|
|
70
66
|
const res = await api.post(`/credentials/${encodeURIComponent(id)}/test`);
|
|
71
67
|
return res?.ok === false ? res : { ok: true, message: res?.message };
|
|
72
68
|
} catch (e) {
|
|
73
|
-
return { ok: false, error:
|
|
69
|
+
return { ok: false, error: errorReason(e, t("integration.credentials.error.testFailed")) };
|
|
74
70
|
}
|
|
75
71
|
}
|
|
76
72
|
async function oauthAuthorizeUrl(id) {
|
|
@@ -24,6 +24,10 @@ export declare function useIntegrationsDeadLetters(): {
|
|
|
24
24
|
globalLoading: any;
|
|
25
25
|
globalLoaded: any;
|
|
26
26
|
globalHasMore: any;
|
|
27
|
+
globalPage: any;
|
|
28
|
+
globalLastPage: import("vue").ComputedRef<number>;
|
|
29
|
+
goGlobalPage: (page: number) => Promise<GlobalDeadLetterRow[]>;
|
|
30
|
+
resetGlobalPage: () => void;
|
|
27
31
|
summary: any;
|
|
28
32
|
pendingCount: import("vue").ComputedRef<any>;
|
|
29
33
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useLoadGate } from "./useLoadGate.js";
|
|
4
|
+
import { runFeedLastPage } from "../utils/runFeedQuery.js";
|
|
4
5
|
const EMPTY_SUMMARY = { total: 0, by_status: {} };
|
|
5
6
|
export function useIntegrationsDeadLetters() {
|
|
6
7
|
const api = useIntegrationsApi();
|
|
@@ -25,12 +26,15 @@ export function useIntegrationsDeadLetters() {
|
|
|
25
26
|
const gate = useLoadGate("integrations.deadletters.global", { loading: globalLoading, loaded: globalLoaded });
|
|
26
27
|
const summary = useState("integrations.deadletters.summary", () => ({ ...EMPTY_SUMMARY }));
|
|
27
28
|
const globalHasMore = useState("integrations.deadletters.global.hasMore", () => false);
|
|
29
|
+
const GLOBAL_PER_PAGE = 25;
|
|
30
|
+
const globalPage = useState("integrations.deadletters.global.page", () => 1);
|
|
31
|
+
const globalLastPage = computed(() => runFeedLastPage(summary.value.total, GLOBAL_PER_PAGE));
|
|
28
32
|
const pendingCount = computed(() => summary.value.by_status.pending ?? 0);
|
|
29
33
|
async function loadGlobal(force = false) {
|
|
30
34
|
if (!await gate.claim(force)) return globalItems.value;
|
|
31
35
|
try {
|
|
32
36
|
const [page, counts] = await Promise.all([
|
|
33
|
-
api.get(
|
|
37
|
+
api.get(`/dead-letters?page=${globalPage.value}&per_page=${GLOBAL_PER_PAGE}`),
|
|
34
38
|
api.get("/dead-letters/summary")
|
|
35
39
|
]);
|
|
36
40
|
globalItems.value = page?.data ?? [];
|
|
@@ -45,6 +49,13 @@ export function useIntegrationsDeadLetters() {
|
|
|
45
49
|
}
|
|
46
50
|
return globalItems.value;
|
|
47
51
|
}
|
|
52
|
+
async function goGlobalPage(page) {
|
|
53
|
+
globalPage.value = Math.min(Math.max(1, page), globalLastPage.value);
|
|
54
|
+
return loadGlobal(true);
|
|
55
|
+
}
|
|
56
|
+
function resetGlobalPage() {
|
|
57
|
+
globalPage.value = 1;
|
|
58
|
+
}
|
|
48
59
|
return {
|
|
49
60
|
list,
|
|
50
61
|
replay,
|
|
@@ -54,6 +65,10 @@ export function useIntegrationsDeadLetters() {
|
|
|
54
65
|
globalLoading,
|
|
55
66
|
globalLoaded,
|
|
56
67
|
globalHasMore,
|
|
68
|
+
globalPage,
|
|
69
|
+
globalLastPage,
|
|
70
|
+
goGlobalPage,
|
|
71
|
+
resetGlobalPage,
|
|
57
72
|
summary,
|
|
58
73
|
pendingCount
|
|
59
74
|
};
|
|
@@ -2,7 +2,7 @@ import { applyNodeCatalog, findManifest } from "../data/integrationNodeCatalog.j
|
|
|
2
2
|
import { resolveLocalized } from "../utils/localize.js";
|
|
3
3
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
4
4
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
5
|
-
import {
|
|
5
|
+
import { errorReason } from "../utils/apiError.js";
|
|
6
6
|
import { useLoadGate } from "./useLoadGate.js";
|
|
7
7
|
const RETRY_COOLDOWN_MS = 3e4;
|
|
8
8
|
export function useIntegrationsNodes() {
|
|
@@ -39,7 +39,7 @@ export function useIntegrationsNodes() {
|
|
|
39
39
|
applyNodeCatalog([]);
|
|
40
40
|
loaded.value = false;
|
|
41
41
|
failedAt.value = Date.now();
|
|
42
|
-
error.value = e
|
|
42
|
+
error.value = errorReason(e, t("integration.nodes.error.loadFailed"));
|
|
43
43
|
} finally {
|
|
44
44
|
gate.release();
|
|
45
45
|
}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import {
|
|
4
|
+
import { errorReason } from "../utils/apiError.js";
|
|
5
5
|
export function useIntegrationsRevisions() {
|
|
6
6
|
const api = useIntegrationsApi();
|
|
7
7
|
const t = useIntegrationsT();
|
|
8
8
|
const loading = ref(false);
|
|
9
9
|
const error = ref(null);
|
|
10
|
-
function captureError(e, fallback) {
|
|
11
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
12
|
-
return e?.message ?? fallback;
|
|
13
|
-
}
|
|
14
10
|
async function list(workflowId, page = 1, perPage = 25) {
|
|
15
11
|
loading.value = true;
|
|
16
12
|
error.value = null;
|
|
@@ -18,7 +14,7 @@ export function useIntegrationsRevisions() {
|
|
|
18
14
|
const res = await api.get(`/workflows/${workflowId}/revisions?page=${page}&per_page=${perPage}`);
|
|
19
15
|
return { revisions: res.data ?? [], meta: res.meta };
|
|
20
16
|
} catch (e) {
|
|
21
|
-
error.value =
|
|
17
|
+
error.value = errorReason(e, t("integration.revisions.error.loadFailed"));
|
|
22
18
|
throw e;
|
|
23
19
|
} finally {
|
|
24
20
|
loading.value = false;
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
import type { RunStatus, RunStepRow, WorkflowRun } from '../types/integrations.js';
|
|
2
|
+
import type { RunFeedQuery } from '../utils/runFeedQuery.js';
|
|
3
|
+
export type { RunFeedQuery };
|
|
2
4
|
interface RunSummary {
|
|
3
5
|
total: number;
|
|
4
6
|
in_flight: number;
|
|
5
7
|
by_status: Record<string, number>;
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Which page of the tenant's list to read.
|
|
9
|
-
*
|
|
10
|
-
* Only the page: `/runs` also narrows by status, trigger kind, workflow, window
|
|
11
|
-
* and text, and none of that is driven from here yet. The shared DataTable
|
|
12
|
-
* narrows and searches what it holds, and it cannot report a filter selection
|
|
13
|
-
* back to the page under `serverMode` — so a narrowing sent to the platform
|
|
14
|
-
* would have nothing to set it. That is the remaining half of PO-231, and the
|
|
15
|
-
* parameters are waiting on the endpoint for it.
|
|
16
|
-
*/
|
|
17
|
-
export interface RunFeedQuery {
|
|
18
|
-
page: number;
|
|
19
|
-
perPage: number;
|
|
20
|
-
}
|
|
21
9
|
export declare function useIntegrationsRuns(): {
|
|
22
10
|
runs: any;
|
|
23
11
|
loading: any;
|
|
@@ -29,6 +17,8 @@ export declare function useIntegrationsRuns(): {
|
|
|
29
17
|
summary: any;
|
|
30
18
|
hasMore: any;
|
|
31
19
|
query: any;
|
|
20
|
+
setQuery: (patch: Partial<RunFeedQuery>) => Promise<WorkflowRun[]>;
|
|
21
|
+
narrowedTotal: any;
|
|
32
22
|
load: (force?: boolean) => Promise<WorkflowRun[]>;
|
|
33
23
|
loadSummary: () => Promise<RunSummary>;
|
|
34
24
|
loadForWorkflow: (workflowId: number, workflowName: string, opts?: {
|
|
@@ -77,4 +67,3 @@ export declare function useIntegrationsRuns(): {
|
|
|
77
67
|
}>;
|
|
78
68
|
getStackTrace: (workflowId: number | string, runId: number | string) => Promise<string>;
|
|
79
69
|
};
|
|
80
|
-
export {};
|
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import {
|
|
4
|
+
import { errorReason } from "../utils/apiError.js";
|
|
5
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_RUN_DIRECTION,
|
|
8
|
+
DEFAULT_RUN_SORT,
|
|
9
|
+
mapRunStatus,
|
|
10
|
+
runFeedNarrowingString,
|
|
11
|
+
runFeedQueryString,
|
|
12
|
+
withRunFeedPatch
|
|
13
|
+
} from "../utils/runFeedQuery.js";
|
|
6
14
|
const EMPTY_SUMMARY = { total: 0, in_flight: 0, by_status: {} };
|
|
7
|
-
function mapStatus(status) {
|
|
8
|
-
switch (status) {
|
|
9
|
-
case "completed":
|
|
10
|
-
return "succeeded";
|
|
11
|
-
case "pending":
|
|
12
|
-
return "queued";
|
|
13
|
-
case "terminated":
|
|
14
|
-
return "failed";
|
|
15
|
-
case "timed_out":
|
|
16
|
-
return "failed";
|
|
17
|
-
case "running":
|
|
18
|
-
return "running";
|
|
19
|
-
case "failed":
|
|
20
|
-
return "failed";
|
|
21
|
-
case "cancelled":
|
|
22
|
-
return "cancelled";
|
|
23
|
-
// An unknown/unexpected status must never surface as a stuck `running` —
|
|
24
|
-
// fail closed so a run the backend can't classify is visibly terminal.
|
|
25
|
-
default:
|
|
26
|
-
return "failed";
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
15
|
export function useIntegrationsRuns() {
|
|
30
16
|
const runs = useState("integrations.runs", () => []);
|
|
31
17
|
const loading = useState("integrations.runs.loading", () => false);
|
|
@@ -36,9 +22,20 @@ export function useIntegrationsRuns() {
|
|
|
36
22
|
const hasMore = useState("integrations.runs.hasMore", () => false);
|
|
37
23
|
const query = useState("integrations.runs.query", () => ({
|
|
38
24
|
page: 1,
|
|
39
|
-
perPage:
|
|
25
|
+
perPage: 25,
|
|
26
|
+
status: [],
|
|
27
|
+
triggerType: "",
|
|
28
|
+
search: "",
|
|
29
|
+
sort: DEFAULT_RUN_SORT,
|
|
30
|
+
direction: DEFAULT_RUN_DIRECTION
|
|
40
31
|
}));
|
|
32
|
+
const narrowedTotal = useState("integrations.runs.narrowedTotal", () => 0);
|
|
33
|
+
const summaryWrite = useState("integrations.runs.summaryWrite", () => 0);
|
|
41
34
|
const gate = useLoadGate("integrations.runs", { loading, loaded });
|
|
35
|
+
function claimSummary() {
|
|
36
|
+
const mine = ++summaryWrite.value;
|
|
37
|
+
return () => mine === summaryWrite.value;
|
|
38
|
+
}
|
|
42
39
|
const SYNC_LIMIT = 5;
|
|
43
40
|
const api = useIntegrationsApi();
|
|
44
41
|
const t = useIntegrationsT();
|
|
@@ -49,7 +46,7 @@ export function useIntegrationsRuns() {
|
|
|
49
46
|
id: String(r.id),
|
|
50
47
|
workflowId: r.workflow_id,
|
|
51
48
|
workflowName: r.workflow_name ?? workflowName,
|
|
52
|
-
status:
|
|
49
|
+
status: mapRunStatus(r.status),
|
|
53
50
|
temporalRunId: r.temporal_run_id ?? null,
|
|
54
51
|
triggerId: r.trigger_handle ?? "",
|
|
55
52
|
// The platform reports what started the run. This was hardcoded to
|
|
@@ -72,14 +69,24 @@ export function useIntegrationsRuns() {
|
|
|
72
69
|
if (!await gate.claim(force)) return runs.value;
|
|
73
70
|
error.value = null;
|
|
74
71
|
try {
|
|
75
|
-
const
|
|
72
|
+
const narrowing = runFeedNarrowingString(query.value);
|
|
73
|
+
const counted = claimSummary();
|
|
76
74
|
const [page, counts] = await Promise.all([
|
|
77
|
-
api.get(`/runs
|
|
78
|
-
api.get(
|
|
75
|
+
api.get(`/runs?${runFeedQueryString(query.value)}`),
|
|
76
|
+
api.get(`/runs/summary${narrowing ? `?${narrowing}` : ""}`)
|
|
79
77
|
]);
|
|
80
78
|
runs.value = (page?.data ?? []).map((r) => mapRun(r));
|
|
81
79
|
hasMore.value = page?.meta?.has_more ?? false;
|
|
82
|
-
|
|
80
|
+
narrowedTotal.value = counts?.data?.total ?? 0;
|
|
81
|
+
if (narrowing) {
|
|
82
|
+
const refreshed = claimSummary();
|
|
83
|
+
void api.get("/runs/summary").then((tenantWide) => {
|
|
84
|
+
if (refreshed()) summary.value = tenantWide?.data ?? { ...EMPTY_SUMMARY };
|
|
85
|
+
}).catch(() => {
|
|
86
|
+
});
|
|
87
|
+
} else if (counted()) {
|
|
88
|
+
summary.value = counts?.data ?? { ...EMPTY_SUMMARY };
|
|
89
|
+
}
|
|
83
90
|
if (!reconciling.value) {
|
|
84
91
|
reconciling.value = true;
|
|
85
92
|
try {
|
|
@@ -99,7 +106,7 @@ export function useIntegrationsRuns() {
|
|
|
99
106
|
}
|
|
100
107
|
loaded.value = true;
|
|
101
108
|
} catch (e) {
|
|
102
|
-
error.value = e
|
|
109
|
+
error.value = errorReason(e, t("integration.runs.error.loadFailed"));
|
|
103
110
|
loaded.value = false;
|
|
104
111
|
} finally {
|
|
105
112
|
gate.release();
|
|
@@ -139,11 +146,12 @@ export function useIntegrationsRuns() {
|
|
|
139
146
|
return result;
|
|
140
147
|
}
|
|
141
148
|
async function loadSummary() {
|
|
149
|
+
const current = claimSummary();
|
|
142
150
|
try {
|
|
143
151
|
const res = await api.get("/runs/summary");
|
|
144
|
-
summary.value = res?.data ?? { ...EMPTY_SUMMARY };
|
|
152
|
+
if (current()) summary.value = res?.data ?? { ...EMPTY_SUMMARY };
|
|
145
153
|
} catch (e) {
|
|
146
|
-
error.value = e
|
|
154
|
+
if (current()) error.value = errorReason(e, t("integration.runs.error.loadFailed"));
|
|
147
155
|
}
|
|
148
156
|
return summary.value;
|
|
149
157
|
}
|
|
@@ -161,7 +169,7 @@ export function useIntegrationsRuns() {
|
|
|
161
169
|
latestPerWorkflow.value = (res?.data ?? []).map((r) => mapRun(r));
|
|
162
170
|
latestLoaded.value = true;
|
|
163
171
|
} catch (e) {
|
|
164
|
-
error.value = e
|
|
172
|
+
error.value = errorReason(e, t("integration.runs.error.loadFailed"));
|
|
165
173
|
latestLoaded.value = false;
|
|
166
174
|
} finally {
|
|
167
175
|
latestGate.release();
|
|
@@ -208,8 +216,13 @@ export function useIntegrationsRuns() {
|
|
|
208
216
|
};
|
|
209
217
|
const getStackTrace = (workflowId, runId) => api.get(`/workflows/${workflowId}/runs/${runId}/stack-trace`).then((r) => r.stack_trace);
|
|
210
218
|
const total = computed(() => summary.value.total);
|
|
211
|
-
const failedCount = computed(() => Object.entries(summary.value.by_status).filter(([status]) =>
|
|
219
|
+
const failedCount = computed(() => Object.entries(summary.value.by_status).filter(([status]) => mapRunStatus(status) === "failed").reduce((sum, [, count]) => sum + count, 0));
|
|
212
220
|
const runningCount = computed(() => summary.value.in_flight);
|
|
221
|
+
async function setQuery(patch) {
|
|
222
|
+
const next = withRunFeedPatch(query.value, patch);
|
|
223
|
+
query.value = next;
|
|
224
|
+
return load(true);
|
|
225
|
+
}
|
|
213
226
|
return {
|
|
214
227
|
runs,
|
|
215
228
|
loading,
|
|
@@ -221,6 +234,8 @@ export function useIntegrationsRuns() {
|
|
|
221
234
|
summary,
|
|
222
235
|
hasMore,
|
|
223
236
|
query,
|
|
237
|
+
setQuery,
|
|
238
|
+
narrowedTotal,
|
|
224
239
|
load,
|
|
225
240
|
loadSummary,
|
|
226
241
|
loadForWorkflow,
|
|
@@ -2,7 +2,7 @@ import { computed } from "vue";
|
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
4
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
|
-
import {
|
|
5
|
+
import { errorReason } from "../utils/apiError.js";
|
|
6
6
|
import { scheduleTimezone } from "../utils/schedule.js";
|
|
7
7
|
export function useIntegrationsSchedules() {
|
|
8
8
|
const api = useIntegrationsApi();
|
|
@@ -70,7 +70,7 @@ export function useIntegrationsSchedules() {
|
|
|
70
70
|
meta.value = res?.meta ?? { total: 0, live_count: 0, timezones: [] };
|
|
71
71
|
loaded.value = true;
|
|
72
72
|
} catch (e) {
|
|
73
|
-
error.value = e
|
|
73
|
+
error.value = errorReason(e, t("integration.schedules.error.loadFailed"));
|
|
74
74
|
loaded.value = false;
|
|
75
75
|
} finally {
|
|
76
76
|
gate.release();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import {
|
|
4
|
+
import { errorReason } from "../utils/apiError.js";
|
|
5
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
6
6
|
const KEY_PATTERN = /^[A-Z0-9_]+$/;
|
|
7
7
|
export function useIntegrationsSecrets() {
|
|
@@ -26,10 +26,6 @@ export function useIntegrationsSecrets() {
|
|
|
26
26
|
function toRecord(r) {
|
|
27
27
|
return { key: r.key ?? "", created_at: r.created_at ?? "", updated_at: r.updated_at ?? "" };
|
|
28
28
|
}
|
|
29
|
-
function captureError(e, fallback) {
|
|
30
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
31
|
-
return e?.message ?? fallback;
|
|
32
|
-
}
|
|
33
29
|
async function load(force = false) {
|
|
34
30
|
if (!await gate.claim(force)) return secrets.value;
|
|
35
31
|
error.value = null;
|
|
@@ -37,7 +33,7 @@ export function useIntegrationsSecrets() {
|
|
|
37
33
|
secrets.value = normalize(await api.get("/secrets"));
|
|
38
34
|
loaded.value = true;
|
|
39
35
|
} catch (e) {
|
|
40
|
-
error.value =
|
|
36
|
+
error.value = errorReason(e, t("integration.secrets.error.loadFailed"));
|
|
41
37
|
} finally {
|
|
42
38
|
gate.release();
|
|
43
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
|
-
import {
|
|
3
|
+
import { errorReason } from "../utils/apiError.js";
|
|
4
4
|
import { resolveLocalized } from "../utils/localize.js";
|
|
5
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
6
6
|
const BLOB_VERSION = "v0-draft";
|
|
@@ -100,9 +100,6 @@ export function useIntegrationsTemplates() {
|
|
|
100
100
|
function unwrap(res) {
|
|
101
101
|
return res?.data ?? res;
|
|
102
102
|
}
|
|
103
|
-
function captureError(e, fallback) {
|
|
104
|
-
return e instanceof IntegrationsApiError ? e.message : e?.message ?? fallback;
|
|
105
|
-
}
|
|
106
103
|
async function load(force = false) {
|
|
107
104
|
if (!await gate.claim(force)) return templates.value;
|
|
108
105
|
error.value = null;
|
|
@@ -112,7 +109,7 @@ export function useIntegrationsTemplates() {
|
|
|
112
109
|
rawDetails.value = {};
|
|
113
110
|
loaded.value = true;
|
|
114
111
|
} catch (e) {
|
|
115
|
-
error.value =
|
|
112
|
+
error.value = errorReason(e, t("integration.templates.state.error.fallback"));
|
|
116
113
|
rawList.value = [];
|
|
117
114
|
rawDetails.value = {};
|
|
118
115
|
loaded.value = true;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
2
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
|
-
import {
|
|
3
|
+
import { errorReason } from "../utils/apiError.js";
|
|
4
4
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
5
|
export function tenantTriggers(workflows, byWorkflow) {
|
|
6
6
|
return workflows.flatMap((wf) => (byWorkflow[wf.id] ?? []).flatMap((t) => {
|
|
@@ -40,7 +40,7 @@ export function useIntegrationsTriggers() {
|
|
|
40
40
|
error.value = workflowsError.value;
|
|
41
41
|
loaded.value = workflowsLoaded.value && !workflowsError.value;
|
|
42
42
|
} catch (e) {
|
|
43
|
-
error.value = e
|
|
43
|
+
error.value = errorReason(e, t("integration.triggers.error.loadFailed"));
|
|
44
44
|
} finally {
|
|
45
45
|
gate.release();
|
|
46
46
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import {
|
|
4
|
+
import { deepClone } from "../utils/deepClone.js";
|
|
5
|
+
import { errorReason } from "../utils/apiError.js";
|
|
5
6
|
import { useLoadGate } from "./useLoadGate.js";
|
|
6
7
|
const BLOB_VERSION = "v0-draft";
|
|
7
8
|
function toCockpit(api) {
|
|
@@ -89,10 +90,6 @@ export function useIntegrationsWorkflows() {
|
|
|
89
90
|
function unwrap(res) {
|
|
90
91
|
return res?.data ?? res;
|
|
91
92
|
}
|
|
92
|
-
function captureError(e, fallback) {
|
|
93
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
94
|
-
return e?.message ?? fallback;
|
|
95
|
-
}
|
|
96
93
|
function remember(api2, into) {
|
|
97
94
|
if (api2.triggers !== void 0) into[Number(api2.id)] = api2.triggers;
|
|
98
95
|
}
|
|
@@ -123,7 +120,7 @@ export function useIntegrationsWorkflows() {
|
|
|
123
120
|
workflows.value = ingestList(list2);
|
|
124
121
|
loaded.value = true;
|
|
125
122
|
} catch (e) {
|
|
126
|
-
error.value =
|
|
123
|
+
error.value = errorReason(e, t("integration.workflows.error.loadFailed"));
|
|
127
124
|
loaded.value = false;
|
|
128
125
|
} finally {
|
|
129
126
|
gate.release();
|
|
@@ -157,7 +154,7 @@ export function useIntegrationsWorkflows() {
|
|
|
157
154
|
async function duplicate(id, name) {
|
|
158
155
|
const source = await get(id);
|
|
159
156
|
if (!source) throw new Error(t("integration.workflows.error.notFound"));
|
|
160
|
-
const blob =
|
|
157
|
+
const blob = deepClone(source.blob);
|
|
161
158
|
const handleMap = /* @__PURE__ */ new Map();
|
|
162
159
|
for (const trigger of blob.triggers ?? []) {
|
|
163
160
|
const fresh = crypto.randomUUID();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed, onScopeDispose, ref, toValue, watch } from "vue";
|
|
2
2
|
import { isVersionExecutable } from "../data/integrationNodeCatalog.js";
|
|
3
3
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
4
|
-
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
|
+
import { IntegrationsApiError, errorReason } from "../utils/apiError.js";
|
|
5
5
|
export function isResolvableValue(v) {
|
|
6
6
|
if (v === void 0 || v === null) return false;
|
|
7
7
|
if (typeof v === "string") {
|
|
@@ -17,7 +17,7 @@ export function isResolvableValue(v) {
|
|
|
17
17
|
function classifyError(err) {
|
|
18
18
|
const status = err instanceof IntegrationsApiError ? err.status : void 0;
|
|
19
19
|
const t = useIntegrationsT();
|
|
20
|
-
const message = err
|
|
20
|
+
const message = errorReason(err, t("integration.nodeConfig.resolve.error.generic"));
|
|
21
21
|
let kind;
|
|
22
22
|
switch (status) {
|
|
23
23
|
case 409:
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How an OAuth connect attempt ended.
|
|
3
|
+
*
|
|
4
|
+
* `completed` is the provider's own answer, relayed by the landing page. `closed` is
|
|
5
|
+
* the operator shutting the window — which says nothing about whether the
|
|
6
|
+
* authorisation went through, so the caller re-reads rather than announcing anything.
|
|
7
|
+
*/
|
|
8
|
+
export type OAuthConnectOutcome = 'completed' | 'closed';
|
|
9
|
+
/**
|
|
10
|
+
* Connecting a credential by OAuth: the popup, and the two ways of finding out it is
|
|
11
|
+
* over.
|
|
12
|
+
*
|
|
13
|
+
* The studio opens the provider's authorisation page in a separate window rather than
|
|
14
|
+
* navigating, so that a half-filled credential form is still there afterwards. Two
|
|
15
|
+
* things can then end the attempt, and both have to be watched, because neither
|
|
16
|
+
* implies the other:
|
|
17
|
+
*
|
|
18
|
+
* - The integrations callback redirects to `/integrations/credentials/oauth-complete`,
|
|
19
|
+
* which posts `integrations-oauth-complete` back to us. This is the success path,
|
|
20
|
+
* and it is the only one that knows the authorisation actually happened.
|
|
21
|
+
* - The operator closes the window instead — cancelling, or completing it somewhere
|
|
22
|
+
* the message could not come back from. Nothing is posted, so the only signal is
|
|
23
|
+
* `popup.closed`, which cannot be subscribed to and has to be polled.
|
|
24
|
+
*
|
|
25
|
+
* **This needs the integrations-side `oauth_return_url` and the provider redirect to
|
|
26
|
+
* be configured**; without them the callback never reaches the landing page and only
|
|
27
|
+
* the close path ever fires. The prerequisite is written out in `oauth-complete.vue`.
|
|
28
|
+
* It lives on the composable rather than at a call site because it is the reason the
|
|
29
|
+
* handshake has two paths at all, and a reader who only opens one of the three callers
|
|
30
|
+
* would otherwise never meet it — which is exactly what happened while this was written
|
|
31
|
+
* out three times and the prerequisite recorded in one of them.
|
|
32
|
+
*
|
|
33
|
+
* `onSettled` is called once, whichever path fires, with what is known. The caller
|
|
34
|
+
* re-reads the credential and decides what to say, because that is where the three
|
|
35
|
+
* genuinely differ: the list refreshes the list, the detail dialog refreshes one
|
|
36
|
+
* credential and tells its parent, and the create dialog says nothing at all because the
|
|
37
|
+
* operator is still looking at the form. Only the waiting is shared.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useOAuthConnect(onSettled: (outcome: OAuthConnectOutcome) => void | Promise<void>): {
|
|
40
|
+
open: (authorizeUrl: string) => void;
|
|
41
|
+
cancel: () => void;
|
|
42
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { onUnmounted } from "vue";
|
|
2
|
+
export function useOAuthConnect(onSettled) {
|
|
3
|
+
let release = null;
|
|
4
|
+
function settle(outcome) {
|
|
5
|
+
if (!release) return;
|
|
6
|
+
release();
|
|
7
|
+
release = null;
|
|
8
|
+
void onSettled(outcome);
|
|
9
|
+
}
|
|
10
|
+
function open(authorizeUrl) {
|
|
11
|
+
release?.();
|
|
12
|
+
const popup = window.open(authorizeUrl, "oauth-connect", "width=600,height=720");
|
|
13
|
+
if (!popup) return;
|
|
14
|
+
const onMessage = (ev) => {
|
|
15
|
+
if (ev.origin !== window.location.origin) return;
|
|
16
|
+
if (ev.data?.type === "integrations-oauth-complete") settle("completed");
|
|
17
|
+
};
|
|
18
|
+
window.addEventListener("message", onMessage);
|
|
19
|
+
const poll = window.setInterval(() => {
|
|
20
|
+
if (popup.closed) settle("closed");
|
|
21
|
+
}, 1e3);
|
|
22
|
+
release = () => {
|
|
23
|
+
window.clearInterval(poll);
|
|
24
|
+
window.removeEventListener("message", onMessage);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function cancel() {
|
|
28
|
+
release?.();
|
|
29
|
+
release = null;
|
|
30
|
+
}
|
|
31
|
+
onUnmounted(cancel);
|
|
32
|
+
return { open, cancel };
|
|
33
|
+
}
|