@revenexx/studio-integrations 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +426 -0
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/Integrations/CanvasShortcutsDialog.d.vue.ts +9 -0
  4. package/dist/runtime/components/Integrations/CanvasShortcutsDialog.vue +53 -0
  5. package/dist/runtime/components/Integrations/CanvasShortcutsDialog.vue.d.ts +9 -0
  6. package/dist/runtime/components/Integrations/EditorTip.vue +2 -15
  7. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +10 -0
  8. package/dist/runtime/components/Integrations/NodeConfigField.vue +94 -49
  9. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +10 -0
  10. package/dist/runtime/components/Integrations/NodeInspector.vue +37 -60
  11. package/dist/runtime/components/Integrations/RunDetailPanel.vue +80 -33
  12. package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +0 -4
  13. package/dist/runtime/components/Integrations/TemplateCard.vue +3 -6
  14. package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +0 -4
  15. package/dist/runtime/components/Integrations/TemplateInstallStep.d.vue.ts +25 -0
  16. package/dist/runtime/components/Integrations/TemplateInstallStep.vue +198 -0
  17. package/dist/runtime/components/Integrations/TemplateInstallStep.vue.d.ts +25 -0
  18. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +2 -2
  19. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +22 -40
  20. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +2 -2
  21. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +94 -65
  22. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +36 -2
  23. package/dist/runtime/components/Integrations/WorkflowEditor.vue +165 -56
  24. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +36 -2
  25. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +6 -2
  26. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +4 -106
  27. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +6 -2
  28. package/dist/runtime/components/Integrations/WorkflowsTable.vue +22 -11
  29. package/dist/runtime/composables/useCanvasShortcuts.d.ts +34 -0
  30. package/dist/runtime/composables/useCanvasShortcuts.js +11 -8
  31. package/dist/runtime/composables/useCredentialTypes.js +3 -9
  32. package/dist/runtime/composables/useIntegrationsCredentials.js +6 -14
  33. package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
  34. package/dist/runtime/composables/useIntegrationsRuns.js +8 -13
  35. package/dist/runtime/composables/useIntegrationsSecrets.js +5 -5
  36. package/dist/runtime/composables/useIntegrationsTemplates.js +5 -8
  37. package/dist/runtime/composables/useIntegrationsTriggers.js +1 -4
  38. package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -7
  39. package/dist/runtime/data/canvasShortcuts.d.ts +208 -0
  40. package/dist/runtime/data/canvasShortcuts.js +68 -0
  41. package/dist/runtime/i18n/messages/de.json +30 -16
  42. package/dist/runtime/i18n/messages/en.json +30 -16
  43. package/dist/runtime/pages/integrations/templates/[slug].vue +22 -132
  44. package/dist/runtime/pages/integrations/workflows/[id].vue +128 -33
  45. package/dist/runtime/utils/apiEnvelope.d.ts +27 -0
  46. package/dist/runtime/utils/apiEnvelope.js +20 -0
  47. package/dist/runtime/utils/configDefaults.d.ts +18 -0
  48. package/dist/runtime/utils/configDefaults.js +7 -0
  49. package/dist/runtime/utils/errorPathAdvice.d.ts +60 -0
  50. package/dist/runtime/utils/errorPathAdvice.js +19 -0
  51. package/dist/runtime/utils/notify.d.ts +2 -2
  52. package/dist/runtime/utils/runEnvelope.d.ts +12 -0
  53. package/dist/runtime/utils/runEnvelope.js +2 -1
  54. package/dist/runtime/utils/selectOptions.d.ts +48 -0
  55. package/dist/runtime/utils/selectOptions.js +19 -0
  56. package/dist/runtime/utils/shortcutLabel.d.ts +40 -0
  57. package/dist/runtime/utils/shortcutLabel.js +48 -0
  58. package/package.json +1 -1
@@ -0,0 +1,19 @@
1
+ import { findManifest } from "../data/integrationNodeCatalog.js";
2
+ export function unwiredErrorPorts(blob) {
3
+ const advice = [];
4
+ for (const node of blob.nodes) {
5
+ const manifest = findManifest(node.nodeSlug, node.nodeVersion);
6
+ if (!manifest) continue;
7
+ const errorPorts = (manifest.outputs ?? []).filter((o) => o.kind === "error" && o.name).map((o) => o.name);
8
+ if (!errorPorts.length) continue;
9
+ if (!isReachable(node, blob, errorPorts)) continue;
10
+ for (const port of errorPorts) {
11
+ const wired = blob.edges.some((e) => e.from.nodeId === node.id && e.from.output === port);
12
+ if (!wired) advice.push({ nodeId: node.id, port });
13
+ }
14
+ }
15
+ return advice;
16
+ }
17
+ function isReachable(node, blob, errorPorts) {
18
+ return blob.edges.some((e) => e.to.nodeId === node.id) || blob.edges.some((e) => e.from.nodeId === node.id && !errorPorts.includes(e.from.output));
19
+ }
@@ -13,10 +13,10 @@ export declare const toast: ((message: string | (() => string | import("vue").Co
13
13
  custom: (component: import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
14
14
  message: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
15
15
  promise: <ToastData>(promise: Promise<ToastData> | (() => Promise<ToastData>), data?: ({
16
+ id?: number | string | undefined;
17
+ class?: string | undefined;
16
18
  action?: (import("vue-sonner").Action | import("vue").Component) | undefined;
17
19
  icon?: import("vue").Component | undefined;
18
- class?: string | undefined;
19
- id?: number | string | undefined;
20
20
  position?: ("top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center") | undefined;
21
21
  toasterId?: string | undefined;
22
22
  component?: import("vue").Component | undefined;
@@ -11,6 +11,13 @@ export type RunLike = Record<string, unknown>;
11
11
  * looks like a run (`status`, `temporal_run_id`) is the run, whatever else it has
12
12
  * on it.
13
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
+ *
14
21
  * This is deliberately generous about the *rest* of the shape. It answers "where
15
22
  * is the run in this body", not "is this a usable run" — the caller decides that,
16
23
  * because a caller following a run has to be able to count an unusable answer as
@@ -28,4 +35,9 @@ export declare const RUN_TERMINAL: Set<string>;
28
35
  export declare function isTerminalRun(run: {
29
36
  status?: unknown;
30
37
  } | null | undefined): boolean;
38
+ /**
39
+ * 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 unwrapped into it.
41
+ */
42
+ export declare const RUN_OWN_FIELDS: readonly ["status", "temporal_run_id"];
31
43
  export declare function unwrapRun(res: unknown): RunLike | null;
@@ -2,10 +2,11 @@ export const RUN_TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "can
2
2
  export function isTerminalRun(run) {
3
3
  return RUN_TERMINAL.has(String(run?.status ?? "").toLowerCase());
4
4
  }
5
+ export const RUN_OWN_FIELDS = ["status", "temporal_run_id"];
5
6
  export function unwrapRun(res) {
6
7
  if (!res || typeof res !== "object") return null;
7
8
  const o = res;
8
- if ("data" in o && o.data && typeof o.data === "object" && !("status" in o) && !("temporal_run_id" in o)) {
9
+ if ("data" in o && o.data && typeof o.data === "object" && !RUN_OWN_FIELDS.some((f) => f in o)) {
9
10
  return o.data;
10
11
  }
11
12
  return o;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Carrying a node's declared options through a select that reserves the empty
3
+ * string for "nothing chosen".
4
+ *
5
+ * "No choice" is a legitimate thing for a node to offer — "as the file came in",
6
+ * "unchanged", "all kinds" — and three node types in two packages arrived at the
7
+ * same way of writing it: an option with an empty value. The select underneath
8
+ * this UI refuses it: the empty string is how it says nothing is selected, so an
9
+ * item may not also carry it. Handed one anyway it throws while the list mounts,
10
+ * and takes the surrounding dialog's dismissal machinery down with it — nothing
11
+ * opened afterwards could be closed again.
12
+ *
13
+ * So every option value is carried under a substitute and swapped back on the way
14
+ * out. **Every** one, rather than only the empty value: a sentinel that stood
15
+ * beside untouched values would be a value a node could declare itself, and
16
+ * declaring it would silently store the empty string instead. Here a declared
17
+ * value always arrives prefixed and the empty one never is, so the two cannot
18
+ * collide whatever a node writes. What the node reads is unchanged.
19
+ */
20
+ /** What the option meaning "no choice" is carried under. */
21
+ export declare const EMPTY_OPTION_VALUE = "option-empty";
22
+ /** Does this list of options ask for the substitute at all? */
23
+ export declare function hasEmptyOption(options: Array<{
24
+ value: unknown;
25
+ }> | undefined): boolean;
26
+ /**
27
+ * An option's value as the select may receive it. That is the guard: the item
28
+ * component cannot be handed an empty value from here, whatever a node declares.
29
+ */
30
+ export declare function toItemValue(value: unknown): string;
31
+ /**
32
+ * The stored value as the select's model — encoded the way the items are, or it
33
+ * matches none of them and the trigger falls back to its placeholder.
34
+ *
35
+ * `null` is read as the empty option alongside the empty string, because that is
36
+ * what the platform hands back for one: Laravel converts an empty string to null
37
+ * anywhere in a request body, so the value chosen here returns as null on the next
38
+ * open and the choice would otherwise be gone. A setting with no value at all is
39
+ * not: nothing is stored for it, and a trigger claiming a choice the config does
40
+ * not hold would be the placeholder's lie in the other direction. A node that
41
+ * offers "no choice" declares it as the field's default, so a step placed on the
42
+ * canvas has the empty string from the start.
43
+ */
44
+ export declare function toModelValue(value: unknown, options: Array<{
45
+ value: unknown;
46
+ }> | undefined): string;
47
+ /** What the select hands back, as the node wants it stored. */
48
+ export declare function fromModelValue(value: string): string;
@@ -0,0 +1,19 @@
1
+ const OPTION_VALUE_PREFIX = "option:";
2
+ export const EMPTY_OPTION_VALUE = "option-empty";
3
+ export function hasEmptyOption(options) {
4
+ return (options ?? []).some((o) => String(o.value) === "");
5
+ }
6
+ export function toItemValue(value) {
7
+ const s = String(value);
8
+ return s === "" ? EMPTY_OPTION_VALUE : OPTION_VALUE_PREFIX + s;
9
+ }
10
+ export function toModelValue(value, options) {
11
+ if (value === "" || value === null || value === void 0) {
12
+ return value !== void 0 && hasEmptyOption(options) ? EMPTY_OPTION_VALUE : "";
13
+ }
14
+ return OPTION_VALUE_PREFIX + String(value);
15
+ }
16
+ export function fromModelValue(value) {
17
+ if (value === EMPTY_OPTION_VALUE) return "";
18
+ return value.startsWith(OPTION_VALUE_PREFIX) ? value.slice(OPTION_VALUE_PREFIX.length) : value;
19
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * How a keyboard shortcut is written for a reader, and which physical modifier
3
+ * `mod` stands for.
4
+ *
5
+ * ONE COPY, deliberately. This table existed twice — in the canvas context
6
+ * menu and in the control pane's tooltip — and the platform test existed a
7
+ * third time inside the shortcuts composable. The two tables had already
8
+ * drifted: the tooltip's knew nothing about `backspace`, `tab` or `space`, so a
9
+ * token it was never handed would have rendered as `BACKSPACE`. A shortcut is
10
+ * about to be shown in a third place (the overview the canvas opens), which is
11
+ * two copies too many.
12
+ *
13
+ * `IS_MAC` reads `navigator.platform`, which is deprecated and reports
14
+ * `MacIntel` for an iPad. It is kept because the replacement
15
+ * (`navigator.userAgentData.platform`) is not in every browser we serve, and
16
+ * because being wrong here costs a wrong glyph rather than a wrong key: the
17
+ * MATCHING side reads the same flag, so a machine we misread is a machine where
18
+ * ⌘ and Ctrl swap together and nothing goes silent.
19
+ */
20
+ export declare const IS_MAC: boolean;
21
+ /**
22
+ * A shortcut token string as a reader sees it: `'mod+enter'` → `⌘↵` on a mac,
23
+ * `Ctrl+↵` elsewhere.
24
+ *
25
+ * Alternatives are not rendered — `'f2|space'` is two ways to press one thing,
26
+ * and a caller that wants both writes both. Pass `isMac` to render for the other
27
+ * platform, which is what the unit suite does.
28
+ */
29
+ /**
30
+ * A token string split into its tokens, where one of the tokens may be the
31
+ * separator itself.
32
+ *
33
+ * `'+'.split('+')` is two empty strings, and `'mod++'` is `['mod', '', '']` — so
34
+ * a pair of empties is a literal `+`. Without this the character disappeared:
35
+ * on a mac, where caps are joined with nothing, `+` rendered as an empty key cap,
36
+ * and elsewhere the join only put it back by accident. Exported so the matcher
37
+ * reads a binding the same way this writes it.
38
+ */
39
+ export declare function shortcutTokens(tokens: string): string[];
40
+ export declare function formatShortcut(tokens: string, isMac?: boolean): string;
@@ -0,0 +1,48 @@
1
+ export const IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "");
2
+ const MAC_TOKENS = {
3
+ mod: "\u2318",
4
+ shift: "\u21E7",
5
+ alt: "\u2325",
6
+ ctrl: "\u2303"
7
+ };
8
+ const OTHER_TOKENS = {
9
+ mod: "Ctrl",
10
+ shift: "Shift",
11
+ alt: "Alt",
12
+ ctrl: "Ctrl"
13
+ };
14
+ const KEY_TOKENS = {
15
+ enter: "\u21B5",
16
+ backspace: "\u232B",
17
+ delete: "Del",
18
+ escape: "Esc",
19
+ tab: "Tab",
20
+ space: "Space",
21
+ arrowup: "\u2191",
22
+ arrowdown: "\u2193",
23
+ arrowleft: "\u2190",
24
+ arrowright: "\u2192"
25
+ };
26
+ export function shortcutTokens(tokens) {
27
+ const parts = tokens.split("+");
28
+ const out = [];
29
+ for (let i = 0; i < parts.length; i++) {
30
+ if (parts[i] === "" && parts[i + 1] === "") {
31
+ out.push("+");
32
+ i++;
33
+ continue;
34
+ }
35
+ if (parts[i] === "") continue;
36
+ out.push(parts[i]);
37
+ }
38
+ return out;
39
+ }
40
+ export function formatShortcut(tokens, isMac = IS_MAC) {
41
+ const mods = isMac ? MAC_TOKENS : OTHER_TOKENS;
42
+ const out = [];
43
+ for (const raw of shortcutTokens(tokens)) {
44
+ const t = raw.toLowerCase();
45
+ out.push(mods[t] ?? KEY_TOKENS[t] ?? (t.length === 1 ? t.toUpperCase() : t.charAt(0).toUpperCase() + t.slice(1)));
46
+ }
47
+ return isMac ? out.join("") : out.join("+");
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
6
6
  "repository": {