@revenexx/studio-integrations 2.10.0 → 3.0.1

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @revenexx/studio-integrations
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 259fe08: A template card whose definition could not be fetched now says "Couldn't load the flow" instead of showing "Loading the flow" for the rest of the visit, on every surface that draws the card. Sorting the gallery by most steps puts templates whose step count is not known after every template whose count is, rather than ranking them as having no steps (PO-249).
8
+
9
+ ## 3.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - a43a347: `<IntegrationsNodeLogo>` is removed; `<IntegrationsNodeMark>` is the one component that draws a node's mark. A host that still calls `<IntegrationsNodeLogo :manifest :size>` writes `<IntegrationsNodeMark :manifest :size logo-only />` instead, which renders exactly what the old component did: the vendor logo, or the node's glyph where there is no logo. `NodeLogo` had been a deprecated stand-in for `NodeMark` since the two were split (PO-194), and no other component, prop or option changes with this major (PO-200).
14
+
3
15
  ## 2.10.0
4
16
 
5
17
  ### Minor Changes
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
3
  "configKey": "studioIntegrations",
4
- "version": "2.10.0",
4
+ "version": "3.0.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { computed, resolveComponent } from "vue";
3
- import { RefreshCircle as Loader2 } from "#solar-icons";
3
+ import { RefreshCircle as Loader2, DangerTriangle as AlertTriangle } from "#solar-icons";
4
4
  import { triggerGlyph as glyphForTrigger } from "../../data/triggerGlyphs";
5
5
  import { templateCategoryLabel, templatePackageLabel } from "../../data/integrationTemplates";
6
6
  import TemplateFlowPreview from "./TemplateFlowPreview.vue";
@@ -68,6 +68,17 @@ const chipSize = computed(() => full.value ? "px-2 py-0.5 text-xs" : "px-1.5 py-
68
68
  :height="full ? 116 : 96"
69
69
  :class="full ? 'mt-7' : ''"
70
70
  />
71
+ <!-- The read came back without a definition. Said in words, because a
72
+ spinner here would wait for the rest of the visit, and the card would
73
+ go on looking like a template still worth waiting for (PO-249). -->
74
+ <div
75
+ v-else-if="template.detailFailed"
76
+ role="status"
77
+ class="flex h-full items-center justify-center gap-1.5 px-3 text-xs text-muted-foreground"
78
+ >
79
+ <AlertTriangle :size="full ? 14 : 12" class="shrink-0" />
80
+ {{ t("integration.templates.card.state.flowUnavailable") }}
81
+ </div>
71
82
  <!-- Named, because "still on its way" and "this template draws nothing"
72
83
  are the same sight otherwise — and a spinner nobody can address is a
73
84
  promise no test can hold to. -->
@@ -95,10 +95,12 @@ export function useIntegrationsTemplates() {
95
95
  const t = useIntegrationsT();
96
96
  const rawList = useState("integrations.templates.raw", () => []);
97
97
  const rawDetails = useState("integrations.templates.rawDetails", () => ({}));
98
+ const failedDetails = useState("integrations.templates.failedDetails", () => ({}));
98
99
  const templates = computed(() => rawList.value.map((item) => {
99
100
  const base = mapListItem(item);
100
101
  const detail = rawDetails.value[item.slug];
101
- return detail ? applyDetail(base, detail) : base;
102
+ if (detail) return applyDetail(base, detail);
103
+ return failedDetails.value[item.slug] ? { ...base, detailFailed: true } : base;
102
104
  }));
103
105
  const loaded = useState("integrations.templates.loaded", () => false);
104
106
  const loading = useState("integrations.templates.loading", () => false);
@@ -112,11 +114,13 @@ export function useIntegrationsTemplates() {
112
114
  const list2 = unwrapList(await api.get("/templates"));
113
115
  rawList.value = list2;
114
116
  rawDetails.value = {};
117
+ failedDetails.value = {};
115
118
  loaded.value = true;
116
119
  } catch (e) {
117
120
  error.value = errorReason(e, t("integration.templates.state.error.fallback"));
118
121
  rawList.value = [];
119
122
  rawDetails.value = {};
123
+ failedDetails.value = {};
120
124
  loaded.value = true;
121
125
  } finally {
122
126
  gate.release();
@@ -142,14 +146,19 @@ export function useIntegrationsTemplates() {
142
146
  });
143
147
  function remember(details) {
144
148
  const found = [...details].filter((entry) => entry[1] !== null);
145
- if (!found.length) return;
146
- rawDetails.value = { ...rawDetails.value, ...Object.fromEntries(found) };
149
+ const missing = [...details].filter(([, detail]) => detail === null).map(([slug]) => slug);
150
+ if (found.length) rawDetails.value = { ...rawDetails.value, ...Object.fromEntries(found) };
151
+ if (found.length || missing.length) {
152
+ const failed = { ...failedDetails.value };
153
+ for (const [slug] of found) delete failed[slug];
154
+ for (const slug of missing) failed[slug] = true;
155
+ failedDetails.value = failed;
156
+ }
147
157
  }
148
158
  async function fetchDetail(slug) {
149
159
  const base = get(slug);
150
160
  if (!base?.fromApi) return base;
151
- const detail = await loader.one(slug);
152
- if (detail) remember(/* @__PURE__ */ new Map([[slug, detail]]));
161
+ remember(/* @__PURE__ */ new Map([[slug, await loader.one(slug)]]));
153
162
  return get(slug);
154
163
  }
155
164
  async function ensureDetails(slugs, _concurrency = 4) {
@@ -1113,6 +1113,7 @@
1113
1113
  "integration.secrets.table.column.key": "Schlüssel",
1114
1114
  "integration.secrets.table.column.updated": "Aktualisiert",
1115
1115
  "integration.shell.action.skipToContent": "Zum Inhalt springen",
1116
+ "integration.templates.card.state.flowUnavailable": "Der Ablauf konnte nicht geladen werden",
1116
1117
  "integration.templates.card.state.loadingFlow": "Der Ablauf wird geladen",
1117
1118
  "integration.templates.card.trigger.event": "Event-gesteuert",
1118
1119
  "integration.templates.card.trigger.schedule": "Geplant",
@@ -1113,6 +1113,7 @@
1113
1113
  "integration.secrets.table.column.key": "Key",
1114
1114
  "integration.secrets.table.column.updated": "Updated",
1115
1115
  "integration.shell.action.skipToContent": "Skip to content",
1116
+ "integration.templates.card.state.flowUnavailable": "Couldn't load the flow",
1116
1117
  "integration.templates.card.state.loadingFlow": "Loading the flow",
1117
1118
  "integration.templates.card.trigger.event": "Event-driven",
1118
1119
  "integration.templates.card.trigger.schedule": "Scheduled",
@@ -49,7 +49,7 @@ const filtered = computed(() => {
49
49
  const sorted = [...items];
50
50
  if (sortBy.value === "name") sorted.sort((a, b) => a.name.localeCompare(b.name));
51
51
  else if (sortBy.value === "level") sorted.sort((a, b) => (LEVEL_RANK[a.level] ?? 0) - (LEVEL_RANK[b.level] ?? 0) || a.name.localeCompare(b.name));
52
- else sorted.sort((a, b) => b.nodeCount - a.nodeCount || a.name.localeCompare(b.name));
52
+ else sorted.sort((a, b) => Number(!!b.detailLoaded) - Number(!!a.detailLoaded) || b.nodeCount - a.nodeCount || a.name.localeCompare(b.name));
53
53
  return sorted;
54
54
  });
55
55
  watch(() => templates.value.length, () => {
@@ -616,6 +616,8 @@ export interface WorkflowTemplate {
616
616
  }>;
617
617
  /** True once the heavy definition blob has been merged via fetchDetail. */
618
618
  detailLoaded?: boolean;
619
+ /** True when the definition was asked for and did not arrive — not "still on its way". */
620
+ detailFailed?: boolean;
619
621
  /** Source node package this template ships with (e.g. Business Central). */
620
622
  package?: {
621
623
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "2.10.0",
3
+ "version": "3.0.1",
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": {