@revenexx/studio-integrations 1.1.1 → 1.3.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 +638 -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 +45 -19
- 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/NodeConfigDynamicSchema.vue +10 -2
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue +88 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +7 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +125 -54
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +7 -3
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +133 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +2 -1
- 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 +129 -18
- 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/StateNamespaceCreateDialog.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue +111 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue.d.ts +16 -0
- 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 +78 -30
- 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/WorkflowStateNamespacesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue +216 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue.d.ts +13 -0
- 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 +7 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +10 -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/integrationNodeCatalog.d.ts +25 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -0
- package/dist/runtime/data/nodeVersionMove.d.ts +72 -0
- package/dist/runtime/data/nodeVersionMove.js +65 -0
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +79 -12
- package/dist/runtime/i18n/messages/en.json +79 -12
- 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 +55 -8
- 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 +169 -24
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
- package/dist/runtime/types/integrations.d.ts +49 -1
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/buildSettle.d.ts +68 -0
- package/dist/runtime/utils/buildSettle.js +29 -0
- 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/settingCondition.d.ts +48 -0
- package/dist/runtime/utils/settingCondition.js +84 -0
- package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
- package/dist/runtime/utils/stateNamespaceName.js +8 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "@revenexx/studio";
|
|
21
21
|
import { toast } from "vue-sonner";
|
|
22
22
|
import { resolveLocalized } from "../../utils/localize";
|
|
23
|
+
import { errorReason } from "../../utils/apiError";
|
|
23
24
|
const props = defineProps({
|
|
24
25
|
open: { type: Boolean, required: true },
|
|
25
26
|
credentialType: { type: [String, Array], required: false }
|
|
@@ -102,24 +103,11 @@ async function runTest() {
|
|
|
102
103
|
testing.value = false;
|
|
103
104
|
}
|
|
104
105
|
}
|
|
106
|
+
const { open: openAuthorisation } = useOAuthConnect(() => {
|
|
107
|
+
void load(true);
|
|
108
|
+
});
|
|
105
109
|
function connect(id) {
|
|
106
|
-
return oauthAuthorizeUrl(id).then(
|
|
107
|
-
const popup = window.open(authorizeUrl, "oauth-connect", "width=600,height=720");
|
|
108
|
-
const onMessage = (ev) => {
|
|
109
|
-
if (ev.data?.type === "integrations-oauth-complete") {
|
|
110
|
-
window.removeEventListener("message", onMessage);
|
|
111
|
-
void load(true);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
window.addEventListener("message", onMessage);
|
|
115
|
-
const poll = window.setInterval(() => {
|
|
116
|
-
if (popup?.closed) {
|
|
117
|
-
window.clearInterval(poll);
|
|
118
|
-
window.removeEventListener("message", onMessage);
|
|
119
|
-
void load(true);
|
|
120
|
-
}
|
|
121
|
-
}, 1e3);
|
|
122
|
-
});
|
|
110
|
+
return oauthAuthorizeUrl(id).then(openAuthorisation);
|
|
123
111
|
}
|
|
124
112
|
async function submit() {
|
|
125
113
|
if (!pickedType.value || !form.name.trim()) return;
|
|
@@ -138,7 +126,7 @@ async function submit() {
|
|
|
138
126
|
emit("created", created.id);
|
|
139
127
|
emit("update:open", false);
|
|
140
128
|
} catch (e) {
|
|
141
|
-
toast.error(e
|
|
129
|
+
toast.error(errorReason(e, t("integration.credentials.createDialog.error.create")));
|
|
142
130
|
} finally {
|
|
143
131
|
saving.value = false;
|
|
144
132
|
}
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
SelectValue
|
|
31
31
|
} from "@revenexx/studio";
|
|
32
32
|
import { toast } from "../../utils/notify";
|
|
33
|
-
import { apiFieldErrors,
|
|
33
|
+
import { apiFieldErrors, errorReason } from "../../utils/apiError";
|
|
34
34
|
import { credentialState, relativeTime } from "../../utils/credentialState";
|
|
35
35
|
import { hasRevenexxSource } from "../../utils/revenexxCredentialSource";
|
|
36
36
|
import { resolveLocalized } from "../../utils/localize";
|
|
@@ -122,7 +122,7 @@ async function save() {
|
|
|
122
122
|
toast.success(t("integration.credentials.toast.updated"));
|
|
123
123
|
} catch (e) {
|
|
124
124
|
fieldErrors.value = apiFieldErrors(e);
|
|
125
|
-
saveError.value =
|
|
125
|
+
saveError.value = errorReason(e, t("integration.credentials.error.save"));
|
|
126
126
|
toast.error(saveError.value);
|
|
127
127
|
} finally {
|
|
128
128
|
saving.value = false;
|
|
@@ -139,36 +139,26 @@ async function destroy() {
|
|
|
139
139
|
emit("changed");
|
|
140
140
|
emit("update:open", false);
|
|
141
141
|
} catch (e) {
|
|
142
|
-
toast.error(e
|
|
142
|
+
toast.error(errorReason(e, t("integration.credentials.error.delete")));
|
|
143
143
|
} finally {
|
|
144
144
|
deleting.value = false;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
const { open: openAuthorisation, cancel: cancelAuthorisation } = useOAuthConnect(async (outcome) => {
|
|
148
|
+
await load(true);
|
|
149
|
+
emit("changed");
|
|
150
|
+
if (outcome === "completed") toast.success(t("integration.credentials.toast.connected"));
|
|
151
|
+
});
|
|
152
|
+
watch(() => props.open, (open) => {
|
|
153
|
+
if (!open) cancelAuthorisation();
|
|
154
|
+
});
|
|
147
155
|
async function connect() {
|
|
148
156
|
if (!props.credential) return;
|
|
149
157
|
connecting.value = true;
|
|
150
158
|
try {
|
|
151
|
-
|
|
152
|
-
const popup = window.open(authorizeUrl, "oauth-connect", "width=600,height=720");
|
|
153
|
-
const onMessage = async (ev) => {
|
|
154
|
-
if (ev.data?.type === "integrations-oauth-complete") {
|
|
155
|
-
window.removeEventListener("message", onMessage);
|
|
156
|
-
await load(true);
|
|
157
|
-
emit("changed");
|
|
158
|
-
toast.success(t("integration.credentials.toast.connected"));
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
window.addEventListener("message", onMessage);
|
|
162
|
-
const poll = window.setInterval(async () => {
|
|
163
|
-
if (popup?.closed) {
|
|
164
|
-
window.clearInterval(poll);
|
|
165
|
-
window.removeEventListener("message", onMessage);
|
|
166
|
-
await load(true);
|
|
167
|
-
emit("changed");
|
|
168
|
-
}
|
|
169
|
-
}, 1e3);
|
|
159
|
+
openAuthorisation(await oauthAuthorizeUrl(props.credential.id));
|
|
170
160
|
} catch (e) {
|
|
171
|
-
toast.error(e
|
|
161
|
+
toast.error(errorReason(e, t("integration.credentials.error.oauthConnect")));
|
|
172
162
|
} finally {
|
|
173
163
|
connecting.value = false;
|
|
174
164
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { toast } from "vue-sonner";
|
|
26
26
|
import { REVENEXX_SOURCE_KIND_BY_SLUG } from "../../utils/revenexxCredentialSource";
|
|
27
27
|
import { cockpitSources } from "../../utils/cockpitSources";
|
|
28
|
+
import { errorReason } from "../../utils/apiError";
|
|
28
29
|
const props = defineProps({
|
|
29
30
|
type: { type: [Object, null], required: true }
|
|
30
31
|
});
|
|
@@ -93,7 +94,7 @@ async function ensureLists() {
|
|
|
93
94
|
await fetchMarkets();
|
|
94
95
|
}
|
|
95
96
|
} catch (e) {
|
|
96
|
-
toast.error(e
|
|
97
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.loadResources")));
|
|
97
98
|
} finally {
|
|
98
99
|
loadingList.value = false;
|
|
99
100
|
}
|
|
@@ -124,7 +125,7 @@ async function applySftp() {
|
|
|
124
125
|
});
|
|
125
126
|
applied.value = `${acc.login_username}@${sftpHost}:${sftpPort}`;
|
|
126
127
|
} catch (e) {
|
|
127
|
-
toast.error(e
|
|
128
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.readSftpPassword")));
|
|
128
129
|
} finally {
|
|
129
130
|
applying.value = false;
|
|
130
131
|
}
|
|
@@ -147,7 +148,7 @@ async function applySmtp() {
|
|
|
147
148
|
});
|
|
148
149
|
applied.value = `${cred.smtp_username}@${mailSmtpHost}:${mailSmtpPort}`;
|
|
149
150
|
} catch (e) {
|
|
150
|
-
toast.error(e
|
|
151
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.readMailCredential")));
|
|
151
152
|
} finally {
|
|
152
153
|
applying.value = false;
|
|
153
154
|
}
|
|
@@ -202,7 +203,7 @@ async function createSftp() {
|
|
|
202
203
|
createOpen.value = false;
|
|
203
204
|
toast.success(t("integration.credentials.revenexxSource.toast.sftpCreated"));
|
|
204
205
|
} catch (e) {
|
|
205
|
-
toast.error(e
|
|
206
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.createSftp")));
|
|
206
207
|
} finally {
|
|
207
208
|
creating.value = false;
|
|
208
209
|
provisioningNote.value = "";
|
|
@@ -230,7 +231,7 @@ async function createSmtp() {
|
|
|
230
231
|
createOpen.value = false;
|
|
231
232
|
toast.success(t("integration.credentials.revenexxSource.toast.smtpCreated"));
|
|
232
233
|
} catch (e) {
|
|
233
|
-
toast.error(e
|
|
234
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.createSmtp")));
|
|
234
235
|
} finally {
|
|
235
236
|
creating.value = false;
|
|
236
237
|
}
|
|
@@ -246,7 +247,7 @@ async function createApiKey() {
|
|
|
246
247
|
createOpen.value = false;
|
|
247
248
|
toast.success(t("integration.credentials.revenexxSource.toast.apiKeyCreated"));
|
|
248
249
|
} catch (e) {
|
|
249
|
-
toast.error(e
|
|
250
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.createApiKey")));
|
|
250
251
|
} finally {
|
|
251
252
|
creating.value = false;
|
|
252
253
|
}
|
|
@@ -3,7 +3,9 @@ import { onMounted, ref, watch } from "vue";
|
|
|
3
3
|
import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
4
|
import { Badge, Button } from "@revenexx/studio";
|
|
5
5
|
import { toast } from "vue-sonner";
|
|
6
|
+
import LoadingAnnouncement from "./LoadingAnnouncement.vue";
|
|
6
7
|
import { deadLetterStatusClass, deadLetterStatusLabel } from "../../utils/statusStyle";
|
|
8
|
+
import { errorReason } from "../../utils/apiError";
|
|
7
9
|
const props = defineProps({
|
|
8
10
|
workflowId: { type: Number, required: true }
|
|
9
11
|
});
|
|
@@ -27,7 +29,7 @@ async function load(p = page.value) {
|
|
|
27
29
|
lastPage.value = res.lastPage;
|
|
28
30
|
page.value = res.page;
|
|
29
31
|
} catch (e) {
|
|
30
|
-
error.value = e
|
|
32
|
+
error.value = errorReason(e, t("integration.deadLetters.error.load"));
|
|
31
33
|
} finally {
|
|
32
34
|
loading.value = false;
|
|
33
35
|
}
|
|
@@ -46,7 +48,7 @@ async function onReplay(dl) {
|
|
|
46
48
|
toast.success(newId ? t("integration.deadLetters.toast.replay.started", { id: newId }) : t("integration.deadLetters.toast.replay.done"));
|
|
47
49
|
await load();
|
|
48
50
|
} catch (e) {
|
|
49
|
-
toast.error(e
|
|
51
|
+
toast.error(errorReason(e, t("integration.deadLetters.toast.replay.error")));
|
|
50
52
|
} finally {
|
|
51
53
|
busyId.value = null;
|
|
52
54
|
}
|
|
@@ -58,7 +60,7 @@ async function onDiscard(dl) {
|
|
|
58
60
|
toast.success(t("integration.deadLetters.toast.discard.done"));
|
|
59
61
|
await load();
|
|
60
62
|
} catch (e) {
|
|
61
|
-
toast.error(e
|
|
63
|
+
toast.error(errorReason(e, t("integration.deadLetters.toast.discard.error")));
|
|
62
64
|
} finally {
|
|
63
65
|
busyId.value = null;
|
|
64
66
|
}
|
|
@@ -75,6 +77,15 @@ function fmt(iso) {
|
|
|
75
77
|
|
|
76
78
|
<template>
|
|
77
79
|
<div class="space-y-3">
|
|
80
|
+
<!-- Outside the branch below, so the words are on the page before the answer is
|
|
81
|
+
(PO-251). This panel is the workflow editor's own copy of the missed-runs
|
|
82
|
+
list; the region is named after the panel rather than the tab. -->
|
|
83
|
+
<LoadingAnnouncement
|
|
84
|
+
:label="t('integration.deadLetters.panel.title')"
|
|
85
|
+
:loading="loading && !items.length"
|
|
86
|
+
:text="t('integration.deadLetters.state.loading')"
|
|
87
|
+
/>
|
|
88
|
+
|
|
78
89
|
<div class="flex items-center justify-between">
|
|
79
90
|
<div>
|
|
80
91
|
<h3 class="text-sm font-semibold">{{ t("integration.deadLetters.panel.title") }}</h3>
|
|
@@ -92,7 +103,7 @@ function fmt(iso) {
|
|
|
92
103
|
{{ error }}
|
|
93
104
|
</div>
|
|
94
105
|
|
|
95
|
-
<div v-if="loading && !items.length">
|
|
106
|
+
<div v-if="loading && !items.length" aria-hidden="true">
|
|
96
107
|
<div class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
|
|
97
108
|
<div v-for="n in 4" :key="`sk-${n}`" class="flex items-start gap-3 px-3 py-3">
|
|
98
109
|
<div class="h-5 w-16 shrink-0 rounded bg-black/[0.07] animate-pulse" />
|
|
@@ -14,6 +14,10 @@ type __VLS_Props = {
|
|
|
14
14
|
invalid?: boolean;
|
|
15
15
|
/** The setting's label, so the editable region has an accessible name. */
|
|
16
16
|
label?: string;
|
|
17
|
+
/** The setting takes several lines: Enter opens one instead of being
|
|
18
|
+
* swallowed, and the box opens at four rows and grows to ten before it
|
|
19
|
+
* scrolls. */
|
|
20
|
+
multiline?: boolean;
|
|
17
21
|
};
|
|
18
22
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
23
|
"update:modelValue": (value: string) => any;
|
|
@@ -14,7 +14,8 @@ const props = defineProps({
|
|
|
14
14
|
variables: { type: Object, required: false },
|
|
15
15
|
upstreamOutputs: { type: Array, required: false },
|
|
16
16
|
invalid: { type: Boolean, required: false },
|
|
17
|
-
label: { type: String, required: false }
|
|
17
|
+
label: { type: String, required: false },
|
|
18
|
+
multiline: { type: Boolean, required: false }
|
|
18
19
|
});
|
|
19
20
|
const emit = defineEmits(["update:modelValue"]);
|
|
20
21
|
const t = useIntegrationsT();
|
|
@@ -76,23 +77,40 @@ const ExprToken = Node.create({
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
});
|
|
80
|
+
const lineMode = () => ({ multiline: props.multiline });
|
|
81
|
+
function editableAttributes() {
|
|
82
|
+
return {
|
|
83
|
+
"role": "textbox",
|
|
84
|
+
...props.multiline ? { "aria-multiline": "true" } : {},
|
|
85
|
+
...props.label ? { "aria-label": props.label } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function endOfLine(line) {
|
|
89
|
+
const doc = editor.state.doc;
|
|
90
|
+
const index = Math.min(Math.max(line, 0), doc.childCount - 1);
|
|
91
|
+
let pos = 0;
|
|
92
|
+
doc.forEach((node, offset, i) => {
|
|
93
|
+
if (i === index) pos = offset + node.nodeSize - 1;
|
|
94
|
+
});
|
|
95
|
+
return pos;
|
|
96
|
+
}
|
|
79
97
|
const editor = new Editor({
|
|
80
98
|
extensions: [Document, Paragraph, Text, History, ExprToken],
|
|
81
|
-
content: textToDoc(text.value),
|
|
99
|
+
content: textToDoc(text.value, lineMode()),
|
|
82
100
|
editorProps: {
|
|
83
|
-
// Single line: swallow Enter (paragraph splits would break the field).
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// otherwise be an anonymous blob to a screen reader.
|
|
88
|
-
attributes: { role: "textbox", ...props.label ? { "aria-label": props.label } : {} }
|
|
101
|
+
// Single line: swallow Enter (paragraph splits would break the field). A
|
|
102
|
+
// multiline field wants exactly that split, so it lets the key through.
|
|
103
|
+
handleKeyDown: (_view, event) => !props.multiline && event.key === "Enter" && !event.isComposing,
|
|
104
|
+
attributes: editableAttributes()
|
|
89
105
|
},
|
|
90
106
|
onUpdate({ editor: ed }) {
|
|
91
107
|
const json = ed.getJSON();
|
|
92
|
-
let raw = docToText(json);
|
|
108
|
+
let raw = docToText(json, lineMode());
|
|
93
109
|
if (hasRawTokenText(json)) {
|
|
94
|
-
ed.
|
|
95
|
-
raw
|
|
110
|
+
const line = ed.state.selection.$from.index(0);
|
|
111
|
+
ed.commands.setContent(textToDoc(raw, lineMode()));
|
|
112
|
+
ed.commands.setTextSelection(endOfLine(line));
|
|
113
|
+
raw = docToText(ed.getJSON(), lineMode());
|
|
96
114
|
}
|
|
97
115
|
if (raw !== text.value) emit("update:modelValue", raw);
|
|
98
116
|
},
|
|
@@ -106,10 +124,14 @@ onBeforeUnmount(() => {
|
|
|
106
124
|
editor.destroy();
|
|
107
125
|
});
|
|
108
126
|
watch(text, (t2) => {
|
|
109
|
-
if (docToText(editor.getJSON()) !== t2) {
|
|
110
|
-
editor.commands.setContent(textToDoc(t2));
|
|
127
|
+
if (docToText(editor.getJSON(), lineMode()) !== t2) {
|
|
128
|
+
editor.commands.setContent(textToDoc(t2, lineMode()));
|
|
111
129
|
}
|
|
112
130
|
});
|
|
131
|
+
watch(() => props.multiline, () => {
|
|
132
|
+
editor.setOptions({ editorProps: { ...editor.options.editorProps, attributes: editableAttributes() } });
|
|
133
|
+
editor.commands.setContent(textToDoc(text.value, lineMode()));
|
|
134
|
+
});
|
|
113
135
|
function insertToken(expr) {
|
|
114
136
|
if (editor.isDestroyed) return;
|
|
115
137
|
editor.chain().focus().insertContent({ type: EXPR_TOKEN_NODE, attrs: { token: expr } }).run();
|
|
@@ -208,14 +230,18 @@ function insert(expr) {
|
|
|
208
230
|
<div ref="wrapRef" class="relative" @focusout="onFocusOut" @keydown="onKeydown">
|
|
209
231
|
<div class="flex items-center gap-1">
|
|
210
232
|
<div
|
|
211
|
-
class="expr-editor relative
|
|
212
|
-
:class="
|
|
233
|
+
class="expr-editor relative min-w-0 flex-1 rounded-md border bg-white px-2 text-sm transition focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-ring"
|
|
234
|
+
:class="[
|
|
235
|
+
multiline ? 'expr-multiline' : 'h-8 overflow-hidden',
|
|
236
|
+
invalid ? 'border-rose-400' : 'border-black/[0.08]'
|
|
237
|
+
]"
|
|
213
238
|
>
|
|
214
239
|
<span
|
|
215
240
|
v-if="!text"
|
|
216
|
-
class="pointer-events-none absolute left-2
|
|
241
|
+
class="pointer-events-none absolute left-2 truncate text-muted-foreground/60"
|
|
242
|
+
:class="multiline ? 'top-1' : 'top-1/2 -translate-y-1/2'"
|
|
217
243
|
>{{ placeholder }}</span>
|
|
218
|
-
<EditorContent :editor="editor" class="h-full" @keydown="onKeydown" />
|
|
244
|
+
<EditorContent :editor="editor" :class="multiline ? '' : 'h-full'" @keydown="onKeydown" />
|
|
219
245
|
</div>
|
|
220
246
|
<!-- The name says what the button does AND what it opens, because the glyph
|
|
221
247
|
says neither; `aria-expanded` says whether the list is there, and
|
|
@@ -230,7 +256,7 @@ function insert(expr) {
|
|
|
230
256
|
:aria-expanded="listOpen"
|
|
231
257
|
:aria-controls="listOpen ? pickerId : void 0"
|
|
232
258
|
class="flex h-8 shrink-0 items-center rounded-md border border-black/[0.08] bg-white px-1.5 font-mono text-sm text-muted-foreground transition hover:text-foreground hover:bg-muted/30"
|
|
233
|
-
:class="listOpen ? 'ring-2 ring-emerald-500/40' : ''"
|
|
259
|
+
:class="[listOpen ? 'ring-2 ring-emerald-500/40' : '', multiline ? 'self-start' : '']"
|
|
234
260
|
@mousedown.prevent="toggle"
|
|
235
261
|
@click="onTriggerClick"
|
|
236
262
|
>
|
|
@@ -296,5 +322,5 @@ function insert(expr) {
|
|
|
296
322
|
</template>
|
|
297
323
|
|
|
298
324
|
<style>
|
|
299
|
-
.expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-pill{align-items:center;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-flex;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;height:20px;line-height:1;margin:0 2px;padding:0 7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
|
|
325
|
+
.expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-editor.expr-multiline{--expr-line:1.375rem;max-height:calc(var(--expr-line)*10 + .5rem);min-height:calc(var(--expr-line)*4 + .5rem);overflow-x:hidden;overflow-y:auto;padding-bottom:.25rem;padding-top:.25rem}.expr-editor.expr-multiline .ProseMirror{display:block;height:auto;overflow-x:visible}.expr-editor.expr-multiline .ProseMirror p{line-height:var(--expr-line);white-space:pre-wrap}.expr-pill{align-items:center;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-flex;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;height:20px;line-height:1;margin:0 2px;padding:0 7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
|
|
300
326
|
</style>
|
|
@@ -14,6 +14,10 @@ type __VLS_Props = {
|
|
|
14
14
|
invalid?: boolean;
|
|
15
15
|
/** The setting's label, so the editable region has an accessible name. */
|
|
16
16
|
label?: string;
|
|
17
|
+
/** The setting takes several lines: Enter opens one instead of being
|
|
18
|
+
* swallowed, and the box opens at four rows and grows to ten before it
|
|
19
|
+
* scrolls. */
|
|
20
|
+
multiline?: boolean;
|
|
17
21
|
};
|
|
18
22
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
23
|
"update:modelValue": (value: string) => any;
|
|
@@ -4,6 +4,7 @@ import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import { ChevronsDownUp, ChevronsUpDown, Ungroup } from "lucide-vue-next";
|
|
6
6
|
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
7
8
|
import { resolveDisplayName } from "../../utils/localize";
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
id: { type: String, required: true },
|
|
@@ -100,9 +101,9 @@ function onResizeEnd() {
|
|
|
100
101
|
v-for="(c, i) in GROUP_COLORS"
|
|
101
102
|
:key="i"
|
|
102
103
|
type="button"
|
|
103
|
-
class="size-
|
|
104
|
+
class="size-6 rounded-full border transition hover:scale-110"
|
|
104
105
|
:class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
|
|
105
|
-
:style="{ backgroundColor: c.chip, borderColor: c.border
|
|
106
|
+
:style="{ backgroundColor: c.chip, borderColor: c.border }"
|
|
106
107
|
:title="t(c.nameKey)"
|
|
107
108
|
:aria-label="t(c.nameKey)"
|
|
108
109
|
@click="data.actions?.setColor(i)"
|
|
@@ -138,7 +139,7 @@ function onResizeEnd() {
|
|
|
138
139
|
<!-- Collapsed: compact stand-in card with generic edge handles. -->
|
|
139
140
|
<template v-if="collapsed">
|
|
140
141
|
<Handle
|
|
141
|
-
id="
|
|
142
|
+
:id="inputHandleId('top')"
|
|
142
143
|
type="target"
|
|
143
144
|
:position="Position.Top"
|
|
144
145
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
@@ -165,7 +166,7 @@ function onResizeEnd() {
|
|
|
165
166
|
</div>
|
|
166
167
|
</div>
|
|
167
168
|
<Handle
|
|
168
|
-
id="
|
|
169
|
+
:id="outputHandleId('default', 'bottom')"
|
|
169
170
|
type="source"
|
|
170
171
|
:position="Position.Bottom"
|
|
171
172
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a surface is doing, in words, for somebody who cannot see the shimmer.
|
|
3
|
+
*
|
|
4
|
+
* A studio surface that is waiting draws placeholder blocks and — before PO-251 —
|
|
5
|
+
* said nothing, which is exactly what it says when there is genuinely nothing to
|
|
6
|
+
* show. Those two states ask for opposite reactions: wait, or go and do something
|
|
7
|
+
* about it. The shimmer separates them for a sighted reader and is not information a
|
|
8
|
+
* screen reader can reach.
|
|
9
|
+
*
|
|
10
|
+
* Three things this component exists to keep from drifting, all of them learned on
|
|
11
|
+
* the template gallery (PO-246), which was the first surface to get this right:
|
|
12
|
+
*
|
|
13
|
+
* - **It is always in the DOM**, and only its text changes. A live region that
|
|
14
|
+
* appears together with its content is a region assistive technology may never
|
|
15
|
+
* have been watching: what gets announced is a change *inside* an existing region.
|
|
16
|
+
* So mount this outside the `v-if` chain it describes, never inside the branch
|
|
17
|
+
* that draws the placeholders.
|
|
18
|
+
* - **It is named**, because a page can hold more than one. The template gallery
|
|
19
|
+
* draws one per card for its flow miniature; the dashboard has four panels that
|
|
20
|
+
* each load on their own. Two unnamed status regions on one page are two things
|
|
21
|
+
* neither a reader nor a test can tell apart.
|
|
22
|
+
* - **`aria-busy` is not what this is.** The attribute only asks assistive
|
|
23
|
+
* technology to defer exposing changes to an element, and on a role-less `div`
|
|
24
|
+
* that is not a live region NVDA and JAWS effectively ignore it — it expresses the
|
|
25
|
+
* state without keeping anything from being walked. The placeholders themselves
|
|
26
|
+
* get `aria-hidden` at the call site instead: a placeholder carries no
|
|
27
|
+
* information, so there is nothing to lose by removing it from the tree.
|
|
28
|
+
*/
|
|
29
|
+
type __VLS_Props = {
|
|
30
|
+
/** Names the region, so several on one page stay distinguishable. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Whether the surface this speaks for is waiting. */
|
|
33
|
+
loading: boolean;
|
|
34
|
+
/** What to say while it is — "Loading the runs…", in the surface's own words. */
|
|
35
|
+
text: string;
|
|
36
|
+
};
|
|
37
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
label: { type: String, required: true },
|
|
4
|
+
loading: { type: Boolean, required: true },
|
|
5
|
+
text: { type: String, required: true }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<p
|
|
11
|
+
role="status"
|
|
12
|
+
aria-live="polite"
|
|
13
|
+
:aria-label="props.label"
|
|
14
|
+
class="sr-only"
|
|
15
|
+
>{{ props.loading ? props.text : "" }}</p>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a surface is doing, in words, for somebody who cannot see the shimmer.
|
|
3
|
+
*
|
|
4
|
+
* A studio surface that is waiting draws placeholder blocks and — before PO-251 —
|
|
5
|
+
* said nothing, which is exactly what it says when there is genuinely nothing to
|
|
6
|
+
* show. Those two states ask for opposite reactions: wait, or go and do something
|
|
7
|
+
* about it. The shimmer separates them for a sighted reader and is not information a
|
|
8
|
+
* screen reader can reach.
|
|
9
|
+
*
|
|
10
|
+
* Three things this component exists to keep from drifting, all of them learned on
|
|
11
|
+
* the template gallery (PO-246), which was the first surface to get this right:
|
|
12
|
+
*
|
|
13
|
+
* - **It is always in the DOM**, and only its text changes. A live region that
|
|
14
|
+
* appears together with its content is a region assistive technology may never
|
|
15
|
+
* have been watching: what gets announced is a change *inside* an existing region.
|
|
16
|
+
* So mount this outside the `v-if` chain it describes, never inside the branch
|
|
17
|
+
* that draws the placeholders.
|
|
18
|
+
* - **It is named**, because a page can hold more than one. The template gallery
|
|
19
|
+
* draws one per card for its flow miniature; the dashboard has four panels that
|
|
20
|
+
* each load on their own. Two unnamed status regions on one page are two things
|
|
21
|
+
* neither a reader nor a test can tell apart.
|
|
22
|
+
* - **`aria-busy` is not what this is.** The attribute only asks assistive
|
|
23
|
+
* technology to defer exposing changes to an element, and on a role-less `div`
|
|
24
|
+
* that is not a live region NVDA and JAWS effectively ignore it — it expresses the
|
|
25
|
+
* state without keeping anything from being walked. The placeholders themselves
|
|
26
|
+
* get `aria-hidden` at the call site instead: a placeholder carries no
|
|
27
|
+
* information, so there is nothing to lose by removing it from the tree.
|
|
28
|
+
*/
|
|
29
|
+
type __VLS_Props = {
|
|
30
|
+
/** Names the region, so several on one page stay distinguishable. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Whether the surface this speaks for is waiting. */
|
|
33
|
+
loading: boolean;
|
|
34
|
+
/** What to say while it is — "Loading the runs…", in the surface's own words. */
|
|
35
|
+
text: string;
|
|
36
|
+
};
|
|
37
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
|
+
export default _default;
|
|
@@ -3,6 +3,7 @@ import { computed } from "vue";
|
|
|
3
3
|
import { Button } from "@revenexx/studio";
|
|
4
4
|
import { IntegrationsApiError } from "../../utils/apiError";
|
|
5
5
|
import { resolveLocalized } from "../../utils/localize";
|
|
6
|
+
import { applyingFields, staleKeys } from "../../utils/settingCondition";
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
slug: { type: String, required: true },
|
|
8
9
|
version: { type: String, required: true },
|
|
@@ -24,6 +25,13 @@ const q = useNodeConfigResolve({
|
|
|
24
25
|
dependsOn: () => props.field.dependsOn
|
|
25
26
|
});
|
|
26
27
|
const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
28
|
+
const applyingResolvedFields = computed(() => applyingFields(resolvedFields.value, props.config));
|
|
29
|
+
function writeResolved(key, value) {
|
|
30
|
+
emit("set", key, value);
|
|
31
|
+
const after = { ...props.config, [key]: value };
|
|
32
|
+
for (const stale of staleKeys(resolvedFields.value, key, after))
|
|
33
|
+
emit("set", stale, void 0);
|
|
34
|
+
}
|
|
27
35
|
</script>
|
|
28
36
|
|
|
29
37
|
<template>
|
|
@@ -67,7 +75,7 @@ const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
|
67
75
|
<legend class="px-1 text-mono text-muted-foreground">
|
|
68
76
|
{{ resolveLocalized(field.label) }}
|
|
69
77
|
</legend>
|
|
70
|
-
<template v-for="f in
|
|
78
|
+
<template v-for="f in applyingResolvedFields" :key="f.key">
|
|
71
79
|
<!-- collision guard: a resolved key that shadows a static config field is skipped -->
|
|
72
80
|
<p v-if="staticKeys.includes(f.key)" class="text-sm text-amber-600">
|
|
73
81
|
{{ t("integration.nodeConfig.dynamicSchema.fieldCollision", { key: f.key }) }}
|
|
@@ -89,7 +97,7 @@ const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
|
89
97
|
:static-keys="staticKeys"
|
|
90
98
|
:sibling-fields="resolvedFields"
|
|
91
99
|
:depth="(depth ?? 0) + 1"
|
|
92
|
-
@set="
|
|
100
|
+
@set="writeResolved"
|
|
93
101
|
/>
|
|
94
102
|
</template>
|
|
95
103
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigField, WorkflowVariable } from '../../types/integrations.js';
|
|
1
|
+
import type { ConfigField, WorkflowStateNamespace, WorkflowStateRole, WorkflowStateVisibility, WorkflowVariable } from '../../types/integrations.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
field: ConfigField;
|
|
4
4
|
config: Record<string, unknown>;
|
|
@@ -11,6 +11,8 @@ type __VLS_Props = {
|
|
|
11
11
|
siblingFields?: ConfigField[];
|
|
12
12
|
depth?: number;
|
|
13
13
|
variables?: Record<string, WorkflowVariable>;
|
|
14
|
+
/** The workflow's declared state namespaces — the options a `state-ref` offers. */
|
|
15
|
+
stateNamespaces?: WorkflowStateNamespace[];
|
|
14
16
|
upstreamOutputs?: Array<{
|
|
15
17
|
nodeId: string;
|
|
16
18
|
nodeName?: string;
|
|
@@ -22,7 +24,9 @@ type __VLS_Props = {
|
|
|
22
24
|
};
|
|
23
25
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
24
26
|
set: (key: string, value: unknown) => any;
|
|
27
|
+
"declare-state": (namespace: string, role: WorkflowStateRole, visibility: WorkflowStateVisibility) => any;
|
|
25
28
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
26
29
|
onSet?: ((key: string, value: unknown) => any) | undefined;
|
|
30
|
+
"onDeclare-state"?: ((namespace: string, role: WorkflowStateRole, visibility: WorkflowStateVisibility) => any) | undefined;
|
|
27
31
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
32
|
export default _default;
|