@revenexx/studio-integrations 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +3 -2
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +3 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +1 -1
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +1 -1
- package/dist/runtime/components/Integrations/TemplateInstallStep.vue +28 -10
- package/dist/runtime/composables/useIntegrationsAudit.d.ts +7 -2
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +9 -5
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +8 -4
- package/dist/runtime/composables/useIntegrationsSchedules.js +6 -3
- package/dist/runtime/composables/useIntegrationsSecrets.d.ts +6 -5
- package/dist/runtime/composables/useIntegrationsSecrets.js +3 -2
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsTemplates.js +5 -6
- package/dist/runtime/composables/useIntegrationsWorkflows.js +4 -4
- package/dist/runtime/composables/useNodeRetirement.js +5 -5
- package/dist/runtime/i18n/messages/de.json +16 -3
- package/dist/runtime/i18n/messages/en.json +16 -3
- package/dist/runtime/pages/integrations/audit.vue +43 -9
- package/dist/runtime/pages/integrations/credentials.vue +3 -2
- package/dist/runtime/pages/integrations/runs.vue +3 -1
- package/dist/runtime/pages/integrations/secrets.vue +3 -1
- package/dist/runtime/pages/integrations/templates/[slug].vue +1 -1
- package/dist/runtime/types/integrations.d.ts +9 -6
- package/dist/runtime/utils/apiEnvelope.d.ts +15 -5
- package/dist/runtime/utils/apiEnvelope.js +11 -8
- package/dist/runtime/utils/credentialTest.d.ts +17 -0
- package/dist/runtime/utils/credentialTest.js +14 -0
- package/dist/runtime/utils/runEnvelope.d.ts +22 -24
- package/dist/runtime/utils/runEnvelope.js +2 -4
- package/package.json +1 -1
- package/dist/runtime/data/integrationWorkflowSamples.d.ts +0 -18
- package/dist/runtime/data/integrationWorkflowSamples.js +0 -318
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
"integration.audit.action.create": "create",
|
|
4
4
|
"integration.audit.action.delete": "delete",
|
|
5
5
|
"integration.audit.action.loadMore": "Load more",
|
|
6
|
+
"integration.audit.action.restore": "restore",
|
|
7
|
+
"integration.audit.action.retire": "retire",
|
|
8
|
+
"integration.audit.action.runCancelled": "run cancelled",
|
|
6
9
|
"integration.audit.action.runFailed": "run failed",
|
|
7
10
|
"integration.audit.action.runFinished": "run finished",
|
|
11
|
+
"integration.audit.action.runResumed": "run resumed",
|
|
8
12
|
"integration.audit.action.runStarted": "run started",
|
|
9
13
|
"integration.audit.action.update": "update",
|
|
10
14
|
"integration.audit.actor.system": "system",
|
|
@@ -14,13 +18,18 @@
|
|
|
14
18
|
"integration.audit.detail.id": "ID",
|
|
15
19
|
"integration.audit.detail.tenant": "Tenant",
|
|
16
20
|
"integration.audit.head.title": "Audit Log · Integration Studio",
|
|
17
|
-
"integration.audit.header.description": "Who changed what, and when. Append-only, per-tenant — credentials, secrets, workflows, triggers and
|
|
21
|
+
"integration.audit.header.description": "Who changed what, and when. Append-only, per-tenant — credentials, secrets, workflows, triggers, runs, node versions and stored state.",
|
|
18
22
|
"integration.audit.header.title": "Audit Log",
|
|
19
23
|
"integration.audit.panel.close.aria": "Close entry details",
|
|
20
24
|
"integration.audit.panel.dismiss.aria": "Dismiss entry details",
|
|
21
25
|
"integration.audit.panel.title.aria": "Audit entry details",
|
|
26
|
+
"integration.audit.resource.run": "Run of workflow #{workflow}",
|
|
27
|
+
"integration.audit.resource.trigger": "{type} trigger of workflow #{workflow}",
|
|
22
28
|
"integration.audit.resourceType.credential": "credential",
|
|
29
|
+
"integration.audit.resourceType.nodeVersion": "node version",
|
|
23
30
|
"integration.audit.resourceType.secret": "secret",
|
|
31
|
+
"integration.audit.resourceType.stateEntry": "state entry",
|
|
32
|
+
"integration.audit.resourceType.stateNamespace": "state namespace",
|
|
24
33
|
"integration.audit.resourceType.trigger": "trigger",
|
|
25
34
|
"integration.audit.resourceType.workflow": "workflow",
|
|
26
35
|
"integration.audit.resourceType.workflowRun": "workflow run",
|
|
@@ -285,6 +294,9 @@
|
|
|
285
294
|
"integration.credentials.shared.field.select.placeholder": "Pick one",
|
|
286
295
|
"integration.credentials.shared.oauthHint": "After saving, a window opens to connect with the provider.",
|
|
287
296
|
"integration.credentials.shared.test.failed": "Connection failed.",
|
|
297
|
+
"integration.credentials.shared.test.notAllowed": "Only someone who may change this credential can test changes to it.",
|
|
298
|
+
"integration.credentials.shared.test.notTested": "Not tested — the platform could not run the test. Nothing is known about the connection.",
|
|
299
|
+
"integration.credentials.shared.test.notTestedBecause": "Not tested — {reason}",
|
|
288
300
|
"integration.credentials.shared.test.ok": "Connection ok.",
|
|
289
301
|
"integration.credentials.state.connected": "Connected",
|
|
290
302
|
"integration.credentials.state.failed": "Failed",
|
|
@@ -1096,10 +1108,10 @@
|
|
|
1096
1108
|
"integration.secrets.dialog.create.title": "Create a secret",
|
|
1097
1109
|
"integration.secrets.dialog.delete.body": "will be permanently removed. Workflows that reference it will break the next time they run.",
|
|
1098
1110
|
"integration.secrets.dialog.delete.title": "Delete secret?",
|
|
1099
|
-
"integration.secrets.error.invalidKey": "Key must match ^[A-
|
|
1111
|
+
"integration.secrets.error.invalidKey": "Key must match ^[A-Za-z0-9_-]+$ and be at most 191 characters",
|
|
1100
1112
|
"integration.secrets.error.loadFailed": "Could not load secrets.",
|
|
1101
1113
|
"integration.secrets.error.valueRequired": "Value is required.",
|
|
1102
|
-
"integration.secrets.form.error.invalidKey": "Key must match ^[A-
|
|
1114
|
+
"integration.secrets.form.error.invalidKey": "Key must match ^[A-Za-z0-9_-]+$ (letters, digits, underscores, hyphens).",
|
|
1103
1115
|
"integration.secrets.form.error.saveFailed": "Could not save the secret.",
|
|
1104
1116
|
"integration.secrets.form.key.label": "Key",
|
|
1105
1117
|
"integration.secrets.form.key.patternLabel": "Pattern:",
|
|
@@ -1156,6 +1168,7 @@
|
|
|
1156
1168
|
"integration.templates.detail.install.secretsDescriptionSuffix": "page.",
|
|
1157
1169
|
"integration.templates.detail.install.secretsHeader": "Secrets",
|
|
1158
1170
|
"integration.templates.detail.install.selectLater": "Select later",
|
|
1171
|
+
"integration.templates.detail.install.settingLabel": "node {id} · {setting}",
|
|
1159
1172
|
"integration.templates.detail.install.state.loading": "Checking what this template needs…",
|
|
1160
1173
|
"integration.templates.detail.install.submit": "Install",
|
|
1161
1174
|
"integration.templates.detail.install.submitting": "Setting up…",
|
|
@@ -7,20 +7,33 @@ import {
|
|
|
7
7
|
Key as Secret,
|
|
8
8
|
Programming as Workflow,
|
|
9
9
|
Bolt as Trigger,
|
|
10
|
-
Play as Run
|
|
10
|
+
Play as Run,
|
|
11
|
+
Box as NodeVersion,
|
|
12
|
+
Database as StateNamespace,
|
|
13
|
+
Document as StateEntry
|
|
11
14
|
} from "#solar-icons";
|
|
12
15
|
import { Badge, Button } from "@revenexx/studio";
|
|
13
16
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
14
17
|
import { isBareIdentifier, personLabel } from "../../utils/personLabel";
|
|
18
|
+
import { triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
15
19
|
const t = useIntegrationsT();
|
|
16
20
|
useHead({ title: () => t("integration.audit.head.title") });
|
|
17
21
|
const { entries, loading, error, hasMore, load, loadMore } = useIntegrationsAudit();
|
|
18
|
-
const resourceTypes = ["credential", "secret", "workflow", "trigger", "workflow_run"];
|
|
19
|
-
const actions = ["create", "update", "delete", "run.started", "run.finished", "run.failed"];
|
|
22
|
+
const resourceTypes = ["credential", "secret", "workflow", "trigger", "workflow_run", "node_version", "state_namespace", "state_entry"];
|
|
23
|
+
const actions = ["create", "update", "delete", "run.started", "run.finished", "run.failed", "run.cancelled", "run.resumed", "retire", "restore"];
|
|
20
24
|
onMounted(() => {
|
|
21
25
|
void load();
|
|
22
26
|
});
|
|
23
|
-
const resourceIcon = {
|
|
27
|
+
const resourceIcon = {
|
|
28
|
+
credential: Credential,
|
|
29
|
+
secret: Secret,
|
|
30
|
+
workflow: Workflow,
|
|
31
|
+
trigger: Trigger,
|
|
32
|
+
workflow_run: Run,
|
|
33
|
+
node_version: NodeVersion,
|
|
34
|
+
state_namespace: StateNamespace,
|
|
35
|
+
state_entry: StateEntry
|
|
36
|
+
};
|
|
24
37
|
function actionTone(a) {
|
|
25
38
|
if (a === "create" || a === "run.finished") return "bg-emerald-500/10 text-emerald-700 border-emerald-500/20";
|
|
26
39
|
if (a === "update") return "bg-amber-500/10 text-amber-700 border-amber-500/20";
|
|
@@ -32,7 +45,10 @@ const resourceTypeKeys = {
|
|
|
32
45
|
secret: "integration.audit.resourceType.secret",
|
|
33
46
|
workflow: "integration.audit.resourceType.workflow",
|
|
34
47
|
trigger: "integration.audit.resourceType.trigger",
|
|
35
|
-
workflow_run: "integration.audit.resourceType.workflowRun"
|
|
48
|
+
workflow_run: "integration.audit.resourceType.workflowRun",
|
|
49
|
+
node_version: "integration.audit.resourceType.nodeVersion",
|
|
50
|
+
state_namespace: "integration.audit.resourceType.stateNamespace",
|
|
51
|
+
state_entry: "integration.audit.resourceType.stateEntry"
|
|
36
52
|
};
|
|
37
53
|
const actionKeys = {
|
|
38
54
|
create: "integration.audit.action.create",
|
|
@@ -40,7 +56,11 @@ const actionKeys = {
|
|
|
40
56
|
delete: "integration.audit.action.delete",
|
|
41
57
|
"run.started": "integration.audit.action.runStarted",
|
|
42
58
|
"run.finished": "integration.audit.action.runFinished",
|
|
43
|
-
"run.failed": "integration.audit.action.runFailed"
|
|
59
|
+
"run.failed": "integration.audit.action.runFailed",
|
|
60
|
+
"run.cancelled": "integration.audit.action.runCancelled",
|
|
61
|
+
"run.resumed": "integration.audit.action.runResumed",
|
|
62
|
+
retire: "integration.audit.action.retire",
|
|
63
|
+
restore: "integration.audit.action.restore"
|
|
44
64
|
};
|
|
45
65
|
function prettyAction(a) {
|
|
46
66
|
const key = actionKeys[a];
|
|
@@ -51,7 +71,18 @@ function prettyType(rt) {
|
|
|
51
71
|
return key ? t(key) : rt.replace("_", " ");
|
|
52
72
|
}
|
|
53
73
|
function resourceName(e) {
|
|
54
|
-
|
|
74
|
+
const m = e.metadata ?? {};
|
|
75
|
+
const text = (v) => typeof v === "string" && v !== "" ? v : void 0;
|
|
76
|
+
const workflow = m.workflow_id != null ? String(m.workflow_id) : void 0;
|
|
77
|
+
const name = text(m.name);
|
|
78
|
+
if (name) return name;
|
|
79
|
+
if (e.resource_type === "trigger" && workflow && text(m.type)) {
|
|
80
|
+
return t("integration.audit.resource.trigger", { type: triggerTypeLabel(text(m.type)), workflow });
|
|
81
|
+
}
|
|
82
|
+
if (e.resource_type === "workflow_run" && workflow) return t("integration.audit.resource.run", { workflow });
|
|
83
|
+
if (e.resource_type === "state_namespace" && text(m.namespace)) return text(m.namespace);
|
|
84
|
+
if (e.resource_type === "state_entry" && text(m.key)) return [text(m.namespace), text(m.key)].filter(Boolean).join(" \xB7 ");
|
|
85
|
+
return e.resource_id || "\u2014";
|
|
55
86
|
}
|
|
56
87
|
function fmtDate(iso) {
|
|
57
88
|
return iso ? new Date(iso).toLocaleString() : "\u2014";
|
|
@@ -68,18 +99,21 @@ function actorIsId(e) {
|
|
|
68
99
|
function actorIsPerson(e) {
|
|
69
100
|
return !!(e.actor_name || e.actor_email);
|
|
70
101
|
}
|
|
102
|
+
function withHeld(known, of) {
|
|
103
|
+
return [...known, ...[...new Set(entries.value.map(of))].filter((v) => v && !known.includes(v)).sort()];
|
|
104
|
+
}
|
|
71
105
|
const filterTabs = computed(() => [
|
|
72
106
|
{
|
|
73
107
|
key: "resource",
|
|
74
108
|
label: t("integration.audit.table.column.resource"),
|
|
75
109
|
getter: (e) => e.resource_type,
|
|
76
|
-
options: resourceTypes.map((rt) => ({ value: rt, label: prettyType(rt) }))
|
|
110
|
+
options: withHeld(resourceTypes, (e) => e.resource_type).map((rt) => ({ value: rt, label: prettyType(rt) }))
|
|
77
111
|
},
|
|
78
112
|
{
|
|
79
113
|
key: "action",
|
|
80
114
|
label: t("integration.audit.table.column.action"),
|
|
81
115
|
getter: (e) => e.action,
|
|
82
|
-
options: actions.map((a) => ({ value: a, label: prettyAction(a) }))
|
|
116
|
+
options: withHeld(actions, (e) => e.action).map((a) => ({ value: a, label: prettyAction(a) }))
|
|
83
117
|
}
|
|
84
118
|
]);
|
|
85
119
|
const columns = computed(() => [
|
|
@@ -37,6 +37,7 @@ import { hasRevenexxSource } from "../../utils/revenexxCredentialSource";
|
|
|
37
37
|
import { resolveLocalized } from "../../utils/localize";
|
|
38
38
|
import { fromModelValue, toItemValue, toModelValue } from "../../utils/selectOptions";
|
|
39
39
|
import { keepOpenBeside } from "../../utils/dialog";
|
|
40
|
+
import { testOutcomeView } from "../../utils/credentialTest";
|
|
40
41
|
const t = useIntegrationsT();
|
|
41
42
|
useHead({ title: () => t("integration.credentials.list.head.title") });
|
|
42
43
|
const { credentials, loading, error, load, create, update, remove, testInline, test, oauthAuthorizeUrl } = useIntegrationsCredentials();
|
|
@@ -462,12 +463,12 @@ async function confirmDelete() {
|
|
|
462
463
|
<div
|
|
463
464
|
v-if="testResult"
|
|
464
465
|
class="rounded-md px-3 py-2 text-sm"
|
|
465
|
-
:class="testResult.
|
|
466
|
+
:class="testOutcomeView(testResult, t).toneClass"
|
|
466
467
|
>
|
|
467
468
|
<!-- What the credential type said, whichever way the test went. The fixed
|
|
468
469
|
sentences are the fallback for a type that reports nothing, not the
|
|
469
470
|
first choice (specs/credential-test.md). -->
|
|
470
|
-
{{ testResult
|
|
471
|
+
{{ testOutcomeView(testResult, t).text }}
|
|
471
472
|
</div>
|
|
472
473
|
|
|
473
474
|
</div>
|
|
@@ -41,7 +41,9 @@ function onReorder(order) {
|
|
|
41
41
|
void setQuery(order);
|
|
42
42
|
}
|
|
43
43
|
let searchTimer;
|
|
44
|
-
|
|
44
|
+
const SEARCH_MAX = 200;
|
|
45
|
+
function onSearch(typed) {
|
|
46
|
+
const text = [...typed].slice(0, SEARCH_MAX).join("");
|
|
45
47
|
clearTimeout(searchTimer);
|
|
46
48
|
if (text === query.value.search) return;
|
|
47
49
|
searchTimer = setTimeout(() => {
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "@revenexx/studio";
|
|
27
27
|
import { errorReason } from "../../utils/apiError";
|
|
28
28
|
import { keepOpenBeside } from "../../utils/dialog";
|
|
29
|
+
import { SECRET_KEY_MAX } from "../../composables/useIntegrationsSecrets";
|
|
29
30
|
const t = useIntegrationsT();
|
|
30
31
|
useHead({ title: () => t("integration.secrets.head.title") });
|
|
31
32
|
const { secrets, loading, error, isValidKey, upsert, remove, load } = useIntegrationsSecrets();
|
|
@@ -121,9 +122,10 @@ const columns = computed(() => [
|
|
|
121
122
|
placeholder="STRIPE_API_KEY"
|
|
122
123
|
class="font-mono bg-white"
|
|
123
124
|
autocomplete="off"
|
|
125
|
+
:maxlength="SECRET_KEY_MAX"
|
|
124
126
|
/>
|
|
125
127
|
<p class="text-sm text-muted-foreground">
|
|
126
|
-
{{ t("integration.secrets.form.key.patternLabel") }} <code class="font-mono">^[A-
|
|
128
|
+
{{ t("integration.secrets.form.key.patternLabel") }} <code class="font-mono">^[A-Za-z0-9_-]+$</code>
|
|
127
129
|
</p>
|
|
128
130
|
</div>
|
|
129
131
|
<div class="space-y-1.5">
|
|
@@ -86,7 +86,7 @@ const triggerChips = computed(() => (tpl.value?.blob.triggers ?? []).map((trig)
|
|
|
86
86
|
const when = describeCron(String(trig.config.cron)) ?? String(trig.config.cron);
|
|
87
87
|
label = `${t("integration.templates.detail.trigger.schedule")} \xB7 ${when}${trig.config.timezone ? ` (${trig.config.timezone})` : ""}`;
|
|
88
88
|
}
|
|
89
|
-
if (trig.type === "webhook" && trig.config?.
|
|
89
|
+
if (trig.type === "webhook" && trig.config?.method) label = `${t("integration.templates.trigger.webhook")} \xB7 ${String(trig.config.method).toUpperCase()}`;
|
|
90
90
|
if (trig.type === "manual") label = t("integration.templates.trigger.manual");
|
|
91
91
|
return { key: trig.id, icon: glyphForTrigger(trig.type), label };
|
|
92
92
|
}));
|
|
@@ -472,7 +472,7 @@ export interface CredentialTypeField {
|
|
|
472
472
|
label: LocalizedString;
|
|
473
473
|
required?: boolean;
|
|
474
474
|
options?: Array<{
|
|
475
|
-
label:
|
|
475
|
+
label: LocalizedString;
|
|
476
476
|
value: string | number | boolean;
|
|
477
477
|
}>;
|
|
478
478
|
validation?: {
|
|
@@ -495,8 +495,10 @@ export interface CredentialType {
|
|
|
495
495
|
slug: string;
|
|
496
496
|
version: string;
|
|
497
497
|
name: LocalizedString;
|
|
498
|
-
|
|
499
|
-
|
|
498
|
+
/** Always sent; null where the descriptor has none. */
|
|
499
|
+
description?: LocalizedString | null;
|
|
500
|
+
/** Always sent; null where the descriptor has none. */
|
|
501
|
+
icon?: string | null;
|
|
500
502
|
/** Signed logo/brand image URLs from the API (prefer `category: 'logo'`). */
|
|
501
503
|
images?: CredentialTypeImage[];
|
|
502
504
|
auth_kind: AuthKind;
|
|
@@ -629,7 +631,8 @@ export interface WorkflowTemplate {
|
|
|
629
631
|
* under `sync` is a name invented by the reader's side of the wire.
|
|
630
632
|
*/
|
|
631
633
|
category: TemplateCategory | '';
|
|
632
|
-
|
|
634
|
+
/** Free-form registry tags; `any` is the one the studio words itself. */
|
|
635
|
+
industries: string[];
|
|
633
636
|
/** Vendor slugs this template wires together (used to compute prerequisites). */
|
|
634
637
|
vendors: string[];
|
|
635
638
|
/** Required secret keys; UI shows which are missing. */
|
|
@@ -725,10 +728,10 @@ export interface RunStepRow {
|
|
|
725
728
|
export interface DeadLetterRow {
|
|
726
729
|
id: number;
|
|
727
730
|
workflow_id: number;
|
|
728
|
-
trigger_id:
|
|
731
|
+
trigger_id: number | null;
|
|
729
732
|
source: string | null;
|
|
730
733
|
trigger_handle: string | null;
|
|
731
|
-
reason: string
|
|
734
|
+
reason: string;
|
|
732
735
|
status: 'pending' | 'replayed' | 'discarded' | string;
|
|
733
736
|
created_at: string | null;
|
|
734
737
|
updated_at: string | null;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One record,
|
|
2
|
+
* One record, out of the `{ data: record }` a wrapping endpoint sends — the
|
|
3
|
+
* template endpoints and `GET /credential-types/{slug}` (see the table above).
|
|
3
4
|
*
|
|
4
|
-
* Throws when the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Throws when the body carries no `data`, a null one, or no body at all — the
|
|
6
|
+
* case the old expression turned into an object with none of the caller's
|
|
7
|
+
* fields, which every page downstream drew as empty.
|
|
7
8
|
*
|
|
8
9
|
* **The thrown message is deliberately empty.** `errorReason(err, fallback)` in
|
|
9
10
|
* `apiError.ts` falls back to the caller's own translated sentence when the
|
|
@@ -11,7 +12,16 @@
|
|
|
11
12
|
* not load this" copy rather than as an untranslated technical string in the UI.
|
|
12
13
|
* The response that caused it rides along in `body`.
|
|
13
14
|
*/
|
|
14
|
-
export declare function unwrapRecord<T>(res: unknown
|
|
15
|
+
export declare function unwrapRecord<T>(res: unknown): T;
|
|
16
|
+
/**
|
|
17
|
+
* One record that IS the body — every single-record endpoint outside templates.
|
|
18
|
+
*
|
|
19
|
+
* Throws, the way `unwrapRecord` does, for no body at all and for a body shaped
|
|
20
|
+
* like an envelope: a `data` key and none of `ownFields`. No version of these
|
|
21
|
+
* endpoints sent one. `ownFields` is for a record that may carry a `data` field of
|
|
22
|
+
* its own — a run (`RUN_OWN_FIELDS`) — so that one is not mistaken for a wrapper.
|
|
23
|
+
*/
|
|
24
|
+
export declare function bareRecord<T>(res: unknown, ownFields?: readonly string[]): T;
|
|
15
25
|
/**
|
|
16
26
|
* A list, from `{ data: [ … ] }` or from a bare array.
|
|
17
27
|
*
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { IntegrationsApiError } from "./apiError.js";
|
|
2
|
-
export function unwrapRecord(res
|
|
3
|
-
|
|
2
|
+
export function unwrapRecord(res) {
|
|
3
|
+
const data = res?.data;
|
|
4
|
+
if (data === null || data === void 0 || typeof data !== "object" || Array.isArray(data)) {
|
|
4
5
|
throw new IntegrationsApiError(200, res, "");
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
export function bareRecord(res, ownFields = []) {
|
|
10
|
+
if (res === null || res === void 0 || typeof res !== "object" || Array.isArray(res)) {
|
|
11
|
+
throw new IntegrationsApiError(200, res, "");
|
|
12
|
+
}
|
|
13
|
+
if ("data" in res && !ownFields.some((f) => f in res)) {
|
|
14
|
+
throw new IntegrationsApiError(200, res, "");
|
|
12
15
|
}
|
|
13
16
|
return res;
|
|
14
17
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CredentialTestResult } from '../composables/useIntegrationsCredentials.js';
|
|
2
|
+
/** What a connection test came to, as the three surfaces that run one draw it. */
|
|
3
|
+
export interface TestOutcomeView {
|
|
4
|
+
kind: 'ok' | 'failed' | 'untested';
|
|
5
|
+
/** The tone of the box: green, red, or neutral for a test that did not happen. */
|
|
6
|
+
toneClass: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One reading of a test result for every surface (specs/credential-test.md).
|
|
11
|
+
*
|
|
12
|
+
* Three answers, not two: a test the platform could not run — its test broker had no
|
|
13
|
+
* room, or the reader may not try an edit — said nothing about the credential, and
|
|
14
|
+
* drawing it red told the operator the connection was broken (IN-22). The words are
|
|
15
|
+
* the platform's where it sent any, the studio's fixed sentence otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export declare function testOutcomeView(result: CredentialTestResult, t: (key: string, params?: Record<string, unknown>) => string): TestOutcomeView;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function testOutcomeView(result, t) {
|
|
2
|
+
if (result.ok) {
|
|
3
|
+
return { kind: "ok", toneClass: "bg-emerald-500/10 text-emerald-700", text: result.message ?? t("integration.credentials.shared.test.ok") };
|
|
4
|
+
}
|
|
5
|
+
if (result.untested) {
|
|
6
|
+
const reason = result.error ?? result.message;
|
|
7
|
+
return {
|
|
8
|
+
kind: "untested",
|
|
9
|
+
toneClass: "bg-slate-500/10 text-slate-700",
|
|
10
|
+
text: reason ? t("integration.credentials.shared.test.notTestedBecause", { reason }) : t("integration.credentials.shared.test.notTested")
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return { kind: "failed", toneClass: "bg-rose-500/10 text-rose-700", text: result.error ?? result.message ?? t("integration.credentials.shared.test.failed") };
|
|
14
|
+
}
|
|
@@ -1,28 +1,5 @@
|
|
|
1
1
|
/** A run as the API sends it, before anything has been read out of it. */
|
|
2
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
|
-
* The two guards are `RUN_OWN_FIELDS`, exported because `unwrapRecord` reads the
|
|
15
|
-
* same endpoint from `useIntegrationsRuns.ts` and has to apply the same rule —
|
|
16
|
-
* one list, two readers, rather than the two literals that used to sit here
|
|
17
|
-
* (PO-394). What stays different is deliberate: this one answers `null` where
|
|
18
|
-
* `unwrapRecord` throws, because a caller following a run has to count an
|
|
19
|
-
* unusable answer as a failed read rather than have it raised at it.
|
|
20
|
-
*
|
|
21
|
-
* This is deliberately generous about the *rest* of the shape. It answers "where
|
|
22
|
-
* is the run in this body", not "is this a usable run" — the caller decides that,
|
|
23
|
-
* because a caller following a run has to be able to count an unusable answer as
|
|
24
|
-
* a failed read rather than sit on it.
|
|
25
|
-
*/
|
|
26
3
|
/**
|
|
27
4
|
* The raw platform statuses a run does not come back from.
|
|
28
5
|
*
|
|
@@ -37,7 +14,28 @@ export declare function isTerminalRun(run: {
|
|
|
37
14
|
} | null | undefined): boolean;
|
|
38
15
|
/**
|
|
39
16
|
* The fields that mark a body as the run itself rather than an envelope around
|
|
40
|
-
* one. A run carrying its own `data` field would otherwise be
|
|
17
|
+
* one. A run carrying its own `data` field would otherwise be taken for an envelope.
|
|
41
18
|
*/
|
|
42
19
|
export declare const RUN_OWN_FIELDS: readonly ["status", "temporal_run_id"];
|
|
20
|
+
/**
|
|
21
|
+
* The run out of a single-run response — the body itself.
|
|
22
|
+
*
|
|
23
|
+
* `GET /workflows/{id}/runs/{runId}` answers the run bare, and no version of it
|
|
24
|
+
* sent an envelope. Until IN-22 this accepted `{ data: run }` as well, because the
|
|
25
|
+
* studio's own mocks wrapped it; that tolerance is what let them stay wrong, so a
|
|
26
|
+
* body shaped like an envelope is now no run at all. "Shaped like an envelope"
|
|
27
|
+
* needs care: a run may carry a `data` field of its own, so a body that already
|
|
28
|
+
* looks like a run (`status`, `temporal_run_id`) is the run, whatever else it has
|
|
29
|
+
* on it.
|
|
30
|
+
*
|
|
31
|
+
* Those two guards are `RUN_OWN_FIELDS`, exported because `bareRecord` reads the
|
|
32
|
+
* same endpoint from `useIntegrationsRuns.ts` and has to apply the same rule —
|
|
33
|
+
* one list, two readers, rather than the two literals that used to sit here
|
|
34
|
+
* (PO-394). What stays different is deliberate: this one answers `null` where
|
|
35
|
+
* `bareRecord` throws, because a caller following a run has to count an
|
|
36
|
+
* unusable answer as a failed read rather than have it raised at it.
|
|
37
|
+
*
|
|
38
|
+
* This is deliberately generous about the *rest* of the shape. It answers "is
|
|
39
|
+
* there a run in this body", not "is this a usable run" — the caller decides that.
|
|
40
|
+
*/
|
|
43
41
|
export declare function unwrapRun(res: unknown): RunLike | null;
|
|
@@ -4,10 +4,8 @@ export function isTerminalRun(run) {
|
|
|
4
4
|
}
|
|
5
5
|
export const RUN_OWN_FIELDS = ["status", "temporal_run_id"];
|
|
6
6
|
export function unwrapRun(res) {
|
|
7
|
-
if (!res || typeof res !== "object") return null;
|
|
7
|
+
if (!res || typeof res !== "object" || Array.isArray(res)) return null;
|
|
8
8
|
const o = res;
|
|
9
|
-
if ("data" in o &&
|
|
10
|
-
return o.data;
|
|
11
|
-
}
|
|
9
|
+
if ("data" in o && !RUN_OWN_FIELDS.some((f) => f in o)) return null;
|
|
12
10
|
return o;
|
|
13
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-integrations",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.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": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Connection, ConnectionTemplate, Workflow } from '../types/integrations.js';
|
|
2
|
-
/**
|
|
3
|
-
* Seed data for the workflow list. Built strictly with the manifests the
|
|
4
|
-
* backend currently has fixtures for (`rest-get`, `control-if`,
|
|
5
|
-
* `control-switch`) and the trigger output port the reference workflow
|
|
6
|
-
* blobs use (`default` — see
|
|
7
|
-
* `playground/integrations/tests/Fixtures/WorkflowBlobs/*.json`).
|
|
8
|
-
*
|
|
9
|
-
* If you change a manifest's `slug` / `version`, change it here too.
|
|
10
|
-
*/
|
|
11
|
-
export declare const SAMPLE_WORKFLOWS: Workflow[];
|
|
12
|
-
export declare const SAMPLE_SECRETS: {
|
|
13
|
-
key: string;
|
|
14
|
-
created_at: string;
|
|
15
|
-
updated_at: string;
|
|
16
|
-
}[];
|
|
17
|
-
export declare const CONNECTION_TEMPLATES: ConnectionTemplate[];
|
|
18
|
-
export declare const SAMPLE_CONNECTIONS: Connection[];
|