@revenexx/studio-integrations 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +483 -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/NodeConfigField.vue +23 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +25 -32
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +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/StickyNoteView.vue +1 -1
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +39 -28
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
- package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
- package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
- package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
- package/dist/runtime/composables/useIntegrationsTemplates.js +2 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +4 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
- package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
- package/dist/runtime/composables/useOAuthConnect.js +33 -0
- package/dist/runtime/data/expressionEditor.d.ts +17 -5
- package/dist/runtime/data/expressionEditor.js +11 -5
- package/dist/runtime/data/handleId.d.ts +61 -1
- package/dist/runtime/data/handleId.js +8 -1
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +26 -8
- package/dist/runtime/i18n/messages/en.json +26 -8
- package/dist/runtime/pages/integrations/audit.vue +7 -6
- package/dist/runtime/pages/integrations/credentials.vue +9 -21
- package/dist/runtime/pages/integrations/index.vue +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 +155 -20
- package/dist/runtime/types/integrations.d.ts +5 -0
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/clipboard.d.ts +33 -0
- package/dist/runtime/utils/clipboard.js +11 -0
- package/dist/runtime/utils/deepClone.d.ts +41 -0
- package/dist/runtime/utils/deepClone.js +3 -0
- package/dist/runtime/utils/formatDuration.d.ts +44 -0
- package/dist/runtime/utils/formatDuration.js +13 -0
- package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
- package/dist/runtime/utils/nodeDisplayName.js +6 -0
- package/dist/runtime/utils/notify.d.ts +1 -1
- package/dist/runtime/utils/runEnvelope.d.ts +31 -0
- package/dist/runtime/utils/runEnvelope.js +12 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
- package/dist/runtime/utils/runFeedQuery.js +66 -0
- package/dist/runtime/utils/runOutputs.d.ts +33 -0
- package/dist/runtime/utils/runOutputs.js +10 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,488 @@
|
|
|
1
1
|
# @revenexx/studio-integrations
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3f56bfa: A studio surface that is waiting now says so in words, not only as a shimmer.
|
|
8
|
+
|
|
9
|
+
Seven surfaces drew placeholder blocks while they loaded and said nothing at all —
|
|
10
|
+
which is exactly what they say when there is genuinely nothing to show. Those two
|
|
11
|
+
states ask for opposite reactions: hold on, or go and do something about it. The
|
|
12
|
+
shimmer that separates them for a sighted reader is not information a screen reader
|
|
13
|
+
can reach, so for one reader every one of these surfaces was silent in the same way,
|
|
14
|
+
whether it was loading or empty.
|
|
15
|
+
|
|
16
|
+
The template gallery had been given words of its own; the other six now have them
|
|
17
|
+
too: the dashboard's four panels, the missed-runs list on the Runs page and inside a
|
|
18
|
+
workflow, the version history, the New-workflow dialog's template picker, and the
|
|
19
|
+
dialog that imports a platform SFTP account. Each says what _it_ is waiting for,
|
|
20
|
+
under its own name, and each stops saying it when the answer arrives. Nothing changes
|
|
21
|
+
on screen for anybody who could already see the shimmer.
|
|
22
|
+
|
|
23
|
+
The dashboard's key figures had the sharper half of the problem, and it was worse
|
|
24
|
+
than silence: while the tenant's workflows were still on their way, each tile
|
|
25
|
+
announced itself as "Active workflows: 0" — a figure counted from an empty list,
|
|
26
|
+
which does not read as waiting but as an answer. A tile is now named by what it
|
|
27
|
+
counts until it has counted it.
|
|
28
|
+
|
|
29
|
+
Behind it, one small component owns the announcement — the region that is always in
|
|
30
|
+
the DOM with only its text changing, the naming that keeps several on one page apart,
|
|
31
|
+
and the placeholders' removal from the accessibility tree — so the seventh surface to
|
|
32
|
+
need it cannot get a different version of it than the first.
|
|
33
|
+
|
|
34
|
+
The eight lists that draw the shared table are not in this: their placeholder rows
|
|
35
|
+
come from `@revenexx/studio-shared` and are carried separately.
|
|
36
|
+
|
|
37
|
+
- 69c9e95: The tenant-wide Runs page now pages and narrows on the platform instead of in the
|
|
38
|
+
browser, so a tenant's whole run history is reachable rather than only its newest
|
|
39
|
+
hundred runs.
|
|
40
|
+
|
|
41
|
+
What changes for anyone using the page. Older runs are reached a page at a time,
|
|
42
|
+
and the count under the table is the number of runs that match rather than the
|
|
43
|
+
number of rows loaded — it used to say "of 100" on a tenant with thousands.
|
|
44
|
+
Narrowing by outcome or trigger kind, and searching by workflow name, are applied
|
|
45
|
+
to the history: "what failed last night" no longer searches a window that on a busy
|
|
46
|
+
tenant can be under an hour, and a narrowing applied from a later page starts again
|
|
47
|
+
at the first one. The Missed runs tab pages the same way, where it previously read
|
|
48
|
+
one page and offered no way past it.
|
|
49
|
+
|
|
50
|
+
Two things are deliberately withheld rather than left to mislead. The columns no
|
|
51
|
+
longer offer a sort, because `GET /runs` orders newest first and takes no ordering
|
|
52
|
+
parameter — a sort here would have reordered one page. And the filter menu shows no
|
|
53
|
+
count per option, because a count of the rows in hand would describe the page
|
|
54
|
+
rather than the history the option narrows.
|
|
55
|
+
|
|
56
|
+
The filter control itself is unchanged in shape: the same menu, the same words, one
|
|
57
|
+
value per dimension.
|
|
58
|
+
|
|
59
|
+
Fixed on the way: the outcome filter sent a status the platform does not have.
|
|
60
|
+
The studio's "failed" stands for several of the platform's statuses, and the
|
|
61
|
+
expansion carried `timed_out`, which its `RunStatus` does not list — so choosing
|
|
62
|
+
"failed" answered `422 The selected status.2 is invalid` instead of narrowing.
|
|
63
|
+
The vocabulary now mirrors the enum, the end-to-end mocks refuse what the service
|
|
64
|
+
refuses, and the suite walks every option of every filter rather than a sample.
|
|
65
|
+
|
|
66
|
+
From review: a search taken back inside the debounce window no longer commits
|
|
67
|
+
itself, a read the platform refuses is now said instead of leaving the previous
|
|
68
|
+
answer standing under the new question, a narrowing that matches nothing says so
|
|
69
|
+
rather than claiming the tenant has never run a workflow, the search box is no
|
|
70
|
+
longer left narrowing the list invisibly after a visit elsewhere, the sidebar's
|
|
71
|
+
Runs badge keeps converging while a filter is applied, and the Missed runs tab
|
|
72
|
+
opens on the newest firings when the dashboard links to it.
|
|
73
|
+
|
|
74
|
+
- 1b2a938: The Runs list can be read in an order other than newest first. Ordering by how
|
|
75
|
+
long a run took answers "which of these is slow?", which the page could not
|
|
76
|
+
answer at all: the headers that used to offer it sorted the hundred runs the
|
|
77
|
+
browser was holding, and were withdrawn when the list moved onto the platform.
|
|
78
|
+
|
|
79
|
+
Two of the six columns offer a sort — when the run started, and how long it took
|
|
80
|
+
— and each orders the whole history rather than the page on screen.
|
|
81
|
+
A run the column cannot describe, one that never started or one still running,
|
|
82
|
+
is listed last whichever way the list is read; asking for another order returns
|
|
83
|
+
to the first page, because re-ordering re-numbers them.
|
|
84
|
+
|
|
85
|
+
The other four headers stay fixed. Three are the platform's shape: ordering by
|
|
86
|
+
the workflow's name would need a join on the path the list pages on, the trigger
|
|
87
|
+
kind has four values so it groups rather than orders, and the run id orders by
|
|
88
|
+
nothing a reader would mean.
|
|
89
|
+
|
|
90
|
+
The outcome column is ours. The platform can order by it, and this page does not
|
|
91
|
+
offer it: it would order by the word the platform stores while the column shows
|
|
92
|
+
the studio's own translated word, so the header would sit over an order the
|
|
93
|
+
reader cannot see — and in German not even an alphabetical one.
|
|
94
|
+
|
|
95
|
+
Needs an integrations service that serves `?sort=` on `GET /v1/runs` (PO-371).
|
|
96
|
+
Against an older one the headers are offered and the platform refuses them.
|
|
97
|
+
|
|
98
|
+
- 19829bf: A workflow can be deleted from the page it is open on. Until now the only way to
|
|
99
|
+
get rid of a workflow was to leave the editor, find it again in the list of all
|
|
100
|
+
of them and use its row menu — the editor offered duplicating, pausing and
|
|
101
|
+
saving, and no way to discard the thing it had open.
|
|
102
|
+
|
|
103
|
+
The header carries an actions menu, and Duplicate has moved into it beside
|
|
104
|
+
Delete. The buttons that remain — Cancel, Save — act on the unsaved work in front
|
|
105
|
+
of the operator; the two in the menu make and unmake the workflow that work
|
|
106
|
+
belongs to. Delete is not a button for a reason: it is the one control there that
|
|
107
|
+
cannot be undone, and it has no business a mis-click away from Save. The question
|
|
108
|
+
it asks is the list's own, unchanged: it names the workflow and says that the
|
|
109
|
+
revision history goes permanently with it.
|
|
110
|
+
|
|
111
|
+
Unsaved work on the canvas raises no second question. The draft belonged to a
|
|
112
|
+
workflow that has stopped existing, so the studio takes it along and moves to the
|
|
113
|
+
workflows list rather than offering to keep work that has nowhere left to go.
|
|
114
|
+
|
|
115
|
+
Both surfaces now say so when the platform refuses a deletion: the question stays
|
|
116
|
+
open carrying the reason, and the workflow stays where it is. The list used to
|
|
117
|
+
fail silently here — the dialog simply stopped, over a row that was still there.
|
|
118
|
+
|
|
119
|
+
- b85a8e0: A run started from the workflow editor now ends on what it returned, whichever mode
|
|
120
|
+
the workflow runs in.
|
|
121
|
+
|
|
122
|
+
Pressing Run on a workflow that does not wait for its result used to answer "Run #99
|
|
123
|
+
started" and stop there — the number was the whole answer, and reading what the run
|
|
124
|
+
actually did meant leaving for the run's own record and watching it fill in. The
|
|
125
|
+
studio now stays with that run: the dialog says where it has got to while it is
|
|
126
|
+
going, and shows what it returned when it ends. The mode a workflow runs in decides
|
|
127
|
+
who waits, not whether the author gets an answer.
|
|
128
|
+
|
|
129
|
+
What the result looks like has changed with it. It used to be the run object printed
|
|
130
|
+
whole — two Temporal identifiers, two re-run references and four timestamps around
|
|
131
|
+
the value somebody pressed Run to see, keyed by the node's internal id. Each value
|
|
132
|
+
now stands under the name its node carries on the canvas, with the run's number,
|
|
133
|
+
status and duration on one line above them; the record itself is still one click
|
|
134
|
+
away, where the rest of it belongs. A returned value that is not a map of this
|
|
135
|
+
workflow's nodes is shown whole rather than filed under a name it may not belong to.
|
|
136
|
+
|
|
137
|
+
The way into the run's record is offered from the moment the run starts, so waiting
|
|
138
|
+
here is never the only option. The studio stops following once the run is terminal,
|
|
139
|
+
once the author closes the result, and after a few reads that cannot be answered —
|
|
140
|
+
the confirmation and the way in stand either way.
|
|
141
|
+
|
|
142
|
+
Behind it: one duration formatter for the runs table, the run record and this
|
|
143
|
+
dialog, which had three identical private copies, and one place that decides what a
|
|
144
|
+
node is called, so the result cannot name a node differently from the canvas.
|
|
145
|
+
|
|
146
|
+
- 1704552: A node setting declared as taking several lines is now drawn with room for them.
|
|
147
|
+
|
|
148
|
+
A node type can say that one of its settings runs to more than one line — a mail
|
|
149
|
+
body, a failure message, a list of instructions one per line — and every layer but
|
|
150
|
+
this one carried the declaration: the node contract has held it since it was
|
|
151
|
+
written, and twelve fields across three published packages set it. The editor's own
|
|
152
|
+
field type did not list it, so it was dropped on arrival and every such setting was
|
|
153
|
+
drawn as the same one-line box. The help text under one of them told the author to
|
|
154
|
+
write one instruction per line at a control that refused the second one.
|
|
155
|
+
|
|
156
|
+
Both controls a typed setting can land in now honour it. The plain box becomes a
|
|
157
|
+
text area, and the box that maps values in from earlier steps takes Enter as a new
|
|
158
|
+
line — with expressions going on working line by line, which matters because that is
|
|
159
|
+
where most of the twelve fields land: a notification mail assembled from an upstream
|
|
160
|
+
value is the ordinary case. Such a setting opens at four lines and grows with what is
|
|
161
|
+
typed until ten, after which it scrolls.
|
|
162
|
+
|
|
163
|
+
Nothing has to be edited in a node package for this: the twelve existing
|
|
164
|
+
declarations start working as they were written. A setting that says nothing about
|
|
165
|
+
lines is drawn exactly as before, and a kind of setting with no multi-line meaning —
|
|
166
|
+
a dropdown — ignores the declaration rather than being made to honour it.
|
|
167
|
+
|
|
168
|
+
### Patch Changes
|
|
169
|
+
|
|
170
|
+
- c94c96e: One place now spells the identifier of a connection point on the canvas, in both
|
|
171
|
+
directions.
|
|
172
|
+
|
|
173
|
+
Nothing changes on screen or in a saved workflow: the ids are byte-identical to the
|
|
174
|
+
ones the canvas drew before, and they were never persisted in a workflow anyway — only
|
|
175
|
+
port names are. What changes is that the twelve places which assembled an id by hand,
|
|
176
|
+
across the node card, the trigger, the group frame, the editor and the two panels that
|
|
177
|
+
rebuild a canvas from a run, now ask one module for it, and the four dot positions are
|
|
178
|
+
declared once instead of six times — including inside the reader, whose own parse is
|
|
179
|
+
built from that one list rather than repeating it.
|
|
180
|
+
|
|
181
|
+
Why it was worth doing while nothing was wrong: the module that reads an id was alone
|
|
182
|
+
there, so a fifth dot position would have had the writers produce a name the reader had
|
|
183
|
+
never heard of, handed back as though it were a port. That is not an empty value, so
|
|
184
|
+
the fallback every call site carries could not have caught it, and the editor would
|
|
185
|
+
have saved an output no node has. The round-trip test now exercises the writers and the
|
|
186
|
+
exported side list rather than a string built in the test, so the two halves are proved
|
|
187
|
+
together.
|
|
188
|
+
|
|
189
|
+
- 31e9a46: The controls the canvas offers on hover are big enough to hit.
|
|
190
|
+
|
|
191
|
+
Deleting a node was the smallest and hardest of them to press, which is the wrong way
|
|
192
|
+
round for the most irreversible thing the editor offers: the button measured 23 by 24
|
|
193
|
+
pixels, a pixel under the minimum a pointer target is allowed to have, and it missed by
|
|
194
|
+
construction rather than by accident — the height was fixed at 24 while the width was
|
|
195
|
+
left to grow from the padding around an 11-pixel glyph. Duplicate beside it, and the
|
|
196
|
+
trigger's delete, were the same.
|
|
197
|
+
|
|
198
|
+
The canvas also disagreed with itself about the size of a toolbar control, in four
|
|
199
|
+
sizes: those two rows, the pair offered on a connection at 20, a sticky note's colour
|
|
200
|
+
swatches at 20, and a group frame's swatches at 16 — the last of them 16 rather than the
|
|
201
|
+
18 their styling claimed, because an inline size was quietly overriding it. Everything a
|
|
202
|
+
hover reveals on a node, a trigger, a sticky note or a group frame is now 24 by 24 with
|
|
203
|
+
a 13-pixel glyph, so a reader learns one target and keeps it.
|
|
204
|
+
|
|
205
|
+
The toolbar on a connection is raised with the rest and is the one that is still not
|
|
206
|
+
promised: it is drawn in the canvas's own coordinates rather than over the top, so its
|
|
207
|
+
24 pixels shrink as the operator zooms out. Better than the 20 it was, and a size is not
|
|
208
|
+
the fix — that is carried separately.
|
|
209
|
+
|
|
210
|
+
- 1c461da: A workflow's own Runs tab no longer draws sort headers that do nothing, and the
|
|
211
|
+
tenant-wide Runs page opens on its first page.
|
|
212
|
+
|
|
213
|
+
On a workflow's Runs tab the column headers looked sortable and were not. Started
|
|
214
|
+
and Duration drew a hover state and a sort icon, took the click and did nothing
|
|
215
|
+
with it — the arrow did not move either, and the tab opened with no column marked
|
|
216
|
+
at all. The headers came back with the tenant-wide list's ordering, which the
|
|
217
|
+
platform performs; this tab renders the same table and the platform cannot order
|
|
218
|
+
its feed.
|
|
219
|
+
|
|
220
|
+
They are no longer offered. The alternative was to make them sort what the browser
|
|
221
|
+
holds, and that is a page: this tab pages on the server, so "order by duration"
|
|
222
|
+
would have put the slowest of the twenty-five rows on screen at the top and said
|
|
223
|
+
nothing about the rest of the history — which is the sentence the tenant-wide list
|
|
224
|
+
withdrew its own headers for until the platform could order the whole thing. The
|
|
225
|
+
runs stand newest first, as they always did, and what would bring an order back is
|
|
226
|
+
recorded in the spec rather than mimed by a control.
|
|
227
|
+
|
|
228
|
+
Two things on the tenant-wide Runs page ride along. Arriving at it now opens on the
|
|
229
|
+
first page: the search box comes back empty whatever was searched for, so a page
|
|
230
|
+
number kept from last time was a position inside a narrowing nothing on screen
|
|
231
|
+
still showed — the outcome and trigger kind are drawn back onto the menu and still
|
|
232
|
+
survive. And arriving with a search still in force now costs one read of the list
|
|
233
|
+
rather than the two it used to.
|
|
234
|
+
|
|
235
|
+
- fae7446: A duration now reads the same wherever the studio prints one.
|
|
236
|
+
|
|
237
|
+
The same step used to say `1.50s` on a run's Details tab and `1.5 s` on its Replay
|
|
238
|
+
tab one click away, and a step that took a minute said `1.1m` in the first place and
|
|
239
|
+
`1m 3s` in the second — which reads as two different figures rather than two
|
|
240
|
+
spellings of one. The dashboard spelled it a third way again — two decimals whatever
|
|
241
|
+
the value, so a run of 900 ms read `0.90s` there and `900ms` in the list it links to.
|
|
242
|
+
Five surfaces printed a duration, across three copies of the arithmetic.
|
|
243
|
+
|
|
244
|
+
They now share one, and its form is `900ms`, `1.5s`, `1m 3s`: the unit against the
|
|
245
|
+
number, as in the `10d ago` beside it, a tenth of a second where one was measured, and
|
|
246
|
+
minutes with their seconds above one — where `1.1m` for a 63-second step was the least
|
|
247
|
+
readable of the three candidates and the second decimal of `1.50s` was precision nobody
|
|
248
|
+
acts on.
|
|
249
|
+
|
|
250
|
+
**A run's duration loses its decimal place entirely, because there was never anything in
|
|
251
|
+
it.** The studio derives it from the two timestamps the platform records to the second,
|
|
252
|
+
so `4.00s` was three characters claiming a precision the platform does not store: that
|
|
253
|
+
run reads `4s` now, and one that took a minute reads `1m`. A step keeps its tenth —
|
|
254
|
+
there the milliseconds are measured, so `3.4s` says something `3s` would not. The replay
|
|
255
|
+
panel loses the space before its unit, and the dashboard stops printing seconds for a
|
|
256
|
+
figure that is not in seconds.
|
|
257
|
+
|
|
258
|
+
Two figures that could not come out right before are gone with it: a duration just
|
|
259
|
+
under a minute printed as `60.0s` — a minute said the other way — and the replay
|
|
260
|
+
panel's own arithmetic could produce `1m 60s`.
|
|
261
|
+
|
|
262
|
+
A duration still takes no language, so German is served `1.5s` rather than `1,5 s`.
|
|
263
|
+
That gap is now written down in one place rather than in four.
|
|
264
|
+
|
|
265
|
+
- d63594c: A refused save now states the reason and not the service's count of everything else it
|
|
266
|
+
found — everywhere, rather than on the two credential surfaces it was reported from.
|
|
267
|
+
|
|
268
|
+
The service refuses a save with one message and a tally: "The name has already been
|
|
269
|
+
taken. (and 2 more errors)". That count is honest arithmetic and misleading copy — it
|
|
270
|
+
counts by validation rule, so one missing field the service checks twice is two of them,
|
|
271
|
+
and it counts things no form draws. PO-275 removed it where it was reported. Creating a
|
|
272
|
+
workflow, creating a credential from the node editor's own lightbox and provisioning a
|
|
273
|
+
revenexx source kept showing it, because each had written the error handling out by hand
|
|
274
|
+
and only the credentials page knew the count was there.
|
|
275
|
+
|
|
276
|
+
The secret form gains a fallback it never had: a refusal whose message was empty drew an
|
|
277
|
+
empty sentence there, because nothing stood behind the message at all.
|
|
278
|
+
|
|
279
|
+
Underneath that: one function now answers what an error should say. It had three
|
|
280
|
+
implementations — the one that knew about the count, a byte-identical private
|
|
281
|
+
`captureError` in six composables, and the cast spelled inline at thirty-seven call
|
|
282
|
+
sites. `refusalReason` is `errorReason`, because a function named for refusals was
|
|
283
|
+
answering "could not load the audit log" in half its callers, and the name is why nobody
|
|
284
|
+
reached for it there. The count of call sites is the reason the sweep had to be done
|
|
285
|
+
twice: the same contract was spelled `(e as { message?: string })?.message`,
|
|
286
|
+
`(e as Error).message`, `(e as Error)?.message` and as an `instanceof` guard with the
|
|
287
|
+
message in each branch, and a search for one of those finds one of those.
|
|
288
|
+
|
|
289
|
+
One behaviour changes on the read paths, and it is small: an error whose message is
|
|
290
|
+
nothing but whitespace used to be drawn as an empty sentence and now falls back to the
|
|
291
|
+
surface's own wording.
|
|
292
|
+
|
|
293
|
+
- b0fcc32: Connecting a credential by OAuth now runs through one handshake instead of three copies
|
|
294
|
+
of it.
|
|
295
|
+
|
|
296
|
+
The **Connect** button exists in three places — a row in the credentials list, the
|
|
297
|
+
credential detail dialog, and the create dialog straight after a credential is made —
|
|
298
|
+
and each carried its own copy of the whole dance:
|
|
299
|
+
open the provider's window, listen for the message the return page posts back, and poll
|
|
300
|
+
once a second in case the operator closes the window instead. The two copies agreed, so
|
|
301
|
+
nothing was wrong on screen; what it cost is that the prerequisite for the message path
|
|
302
|
+
ever firing was documented in only one of them, and a fix to one button did not reach
|
|
303
|
+
the other.
|
|
304
|
+
|
|
305
|
+
Three things that were wrong in both copies are fixed by having one:
|
|
306
|
+
|
|
307
|
+
- **The poll kept running after the message arrived.** The listener removed itself and
|
|
308
|
+
reloaded, but the one-second timer stayed up until the window closed, and then
|
|
309
|
+
reloaded a second time. Harmless while the reload is idempotent, which is the sort of
|
|
310
|
+
thing that stops being true quietly.
|
|
311
|
+
- **No copy stopped when its caller went away.** Closing the detail dialog, or leaving
|
|
312
|
+
the page, while the provider's window was still open left a timer and a listener
|
|
313
|
+
running — and the detail dialog is held open by a prop rather than by `v-if`, so
|
|
314
|
+
closing it unmounts nothing. An attempt begun there could announce a connection, and
|
|
315
|
+
refresh the row, for whichever credential the operator opened next.
|
|
316
|
+
- **A second attempt while one was outstanding left the first still watching**, so a
|
|
317
|
+
window the operator had stopped looking at could still trigger a reload.
|
|
318
|
+
- **The completion message was accepted from any sender.** The landing page posts it to
|
|
319
|
+
our own origin, but nothing checked where it came from — and the window we open holds
|
|
320
|
+
a live `window.opener` on us, so a provider page could have announced a connection
|
|
321
|
+
that never happened. The origin is checked now.
|
|
322
|
+
- **A window the browser refuses to open left the poll running for ever.** `window.open`
|
|
323
|
+
returns `null`, `null?.closed` is `undefined`, and neither path could ever settle.
|
|
324
|
+
Nothing is installed in that case now. The operator is still told nothing, which is
|
|
325
|
+
what all three copies did — that is PO-399.
|
|
326
|
+
|
|
327
|
+
What an operator sees is unchanged: the connection is announced when the provider
|
|
328
|
+
answers, and closing the window refreshes quietly without claiming anything — because
|
|
329
|
+
a closed window says nothing about whether the authorisation went through.
|
|
330
|
+
|
|
331
|
+
- f6cb6e2: The deep copy every editing surface takes now comes from one module, and that module
|
|
332
|
+
says what the copy keeps.
|
|
333
|
+
|
|
334
|
+
Nothing changes on screen. Opening a workflow, editing a node, opening the variables
|
|
335
|
+
dialog, duplicating a workflow and building a manual run's payload all begin by copying
|
|
336
|
+
what they are about to let somebody edit, so an unsaved change can be dropped rather
|
|
337
|
+
than undone. That copy was written out eight times — as the same private one-line
|
|
338
|
+
`clone<T>` in the canvas and in the node inspector, and as a bare
|
|
339
|
+
`JSON.parse(JSON.stringify(…))` in six more places. All eight produced the same value,
|
|
340
|
+
and they still do: this is the same round trip, moved.
|
|
341
|
+
|
|
342
|
+
What was missing is the sentence underneath them. The round trip is safe **because a
|
|
343
|
+
workflow blob is JSON** — that is the entire guarantee, and it was written down nowhere,
|
|
344
|
+
so nothing said which inputs it holds for. A caller reaching for the same expression
|
|
345
|
+
with editor state that has not been through the API — a resolved sample, a cached
|
|
346
|
+
schema, anything holding a `Date`, a `Map` or an `undefined` — would lose those values
|
|
347
|
+
silently, in whichever of the eight places they copied from. The guarantee, and the
|
|
348
|
+
five things the round trip does not survive, are now in one doc comment with a unit
|
|
349
|
+
test that pins each of them.
|
|
350
|
+
|
|
351
|
+
`structuredClone` is deliberately not used, and the reason is in the module: it
|
|
352
|
+
preserves all five, which sounds strictly better and is a behaviour change — a field
|
|
353
|
+
dropped today because JSON cannot carry it would start surviving, and something
|
|
354
|
+
downstream may be relying on that loss. Swapping the mechanism is now one edit, and it
|
|
355
|
+
wants its own proof.
|
|
356
|
+
|
|
357
|
+
- 2271245: What the canvas does with a connection dot's side is now three exhaustive maps instead
|
|
358
|
+
of ternary chains scattered across five components.
|
|
359
|
+
|
|
360
|
+
A dot sits on one of four sides, and three separate answers follow from that: where Vue
|
|
361
|
+
Flow draws it, which side an edge arriving from it should land on, and where along the
|
|
362
|
+
side it is spread when a node has several outputs. Each was a chain of `if`s ending in
|
|
363
|
+
an `else`, inside a component: the position one in both the node card and the trigger
|
|
364
|
+
card, and the opposite-side one in the editor and in both read-only canvases.
|
|
365
|
+
|
|
366
|
+
Nothing changes on screen — the four sides get the same answers. What changes is what
|
|
367
|
+
happens to a fifth. An `else` gives a name it has never heard the last branch's answer:
|
|
368
|
+
a `centre` dot would have been drawn on the **left**, told that `right` faces it, and
|
|
369
|
+
spread along the horizontal axis — three plausible answers, no type error, and nothing
|
|
370
|
+
to notice. The maps are `Record<Side, …>`, so adding a name to `SIDES` stops all three
|
|
371
|
+
compiling until they say what it means.
|
|
372
|
+
|
|
373
|
+
This is the follow-up PO-258 wrote down for itself. Its note said three functions were
|
|
374
|
+
duplicated, and named them; two were, and one of those was hiding under a second name
|
|
375
|
+
(`opposite`, not `oppositeSide`) in the two read-only canvases. The note now says so.
|
|
376
|
+
|
|
377
|
+
- 759d21f: A copy control now says what actually happened, and all seven of them say it the same way.
|
|
378
|
+
|
|
379
|
+
The audit log's copy control announced "Copied" the instant it was pressed rather than
|
|
380
|
+
after the copy succeeded. `writeText` returns a promise, and a browser can refuse it —
|
|
381
|
+
a page that is not focused, a context that is not secure, a permission the operator
|
|
382
|
+
declined. The message had already gone up by then, and nothing contradicted it: the
|
|
383
|
+
operator believed they had the entry on their clipboard and pasted whatever was there
|
|
384
|
+
before. It could also do nothing at all — the control guarded on `navigator.clipboard`
|
|
385
|
+
existing and skipped the whole block when it did not, so on a browser without the API
|
|
386
|
+
the button neither copied nor complained.
|
|
387
|
+
|
|
388
|
+
Both are gone. A refused copy now says so, in English and German.
|
|
389
|
+
|
|
390
|
+
One more had the same defect and was found in review: the expression chip in the node
|
|
391
|
+
inspector, which copies an expression when no field is focused. It discarded the promise,
|
|
392
|
+
short-circuited to nothing when the clipboard API was absent, and said "Expression
|
|
393
|
+
copied" either way. It now reports a failure and keeps the useful half of the hint —
|
|
394
|
+
that clicking into a field inserts the expression directly, which still works when the
|
|
395
|
+
clipboard does not.
|
|
396
|
+
|
|
397
|
+
The other five — node config, rotated secret, webhook URL, a variable's expression,
|
|
398
|
+
nodes copied on the canvas — already behaved correctly, each with its own copy of the
|
|
399
|
+
await-and-catch. All seven now go through one helper, so the next one cannot be written
|
|
400
|
+
the wrong way by having nothing to copy from. Each keeps its own two
|
|
401
|
+
messages: copying a node's config, a webhook URL and an audit entry are three different
|
|
402
|
+
things to have failed, and one message covering all of them would name none of them.
|
|
403
|
+
|
|
404
|
+
- 70bbddc: A saved workflow stops claiming its build is queued once it is not.
|
|
405
|
+
|
|
406
|
+
Pressing Save queues a bundle build, and the editor put "Build queued…" on the toolbar
|
|
407
|
+
and left it there — the build finishes on the platform within a second or two, and the
|
|
408
|
+
chip stayed up for as long as the page was open. Reloading showed everything saved. A
|
|
409
|
+
chip that never clears is read as a save that did not happen, and it was reported as
|
|
410
|
+
exactly that.
|
|
411
|
+
|
|
412
|
+
What resolved it before was a platform announcement over the event bus, and nothing
|
|
413
|
+
else — so a host that does not have that channel, one where it is down, and a
|
|
414
|
+
connection that dropped in the second after the save all produced the same permanently
|
|
415
|
+
wrong screen. The announcement is still the fast path; under it, every way into a queued
|
|
416
|
+
build now watches it out, on the same interval and the same ceiling the Run button
|
|
417
|
+
already used when it had to wait a build out: an ordinary save, the resend-as-paused
|
|
418
|
+
after the deactivation dialog, putting an earlier version back, and opening a workflow
|
|
419
|
+
whose build is already running. That last one is the reload an operator reaches for
|
|
420
|
+
when the chip looks stuck, and it used to land on a chip that was stuck for good. A
|
|
421
|
+
build that fails reaches its notice the same way.
|
|
422
|
+
|
|
423
|
+
- 47fedc8: The validation banner and the card's badge now name a setting the way the node's own form names it.
|
|
424
|
+
|
|
425
|
+
A workflow that cannot be activated yet is reported in an amber banner listing the
|
|
426
|
+
settings still to fix, one line per node. Those settings were named by their internal
|
|
427
|
+
key, taken from the path the service reports the finding at — so an operator read
|
|
428
|
+
"Bestellung anlegen — credentials, companyId, customerId" and then opened the node to
|
|
429
|
+
find three fields called **Revenexx-Anmeldedaten**, **Firma** and **Kunde**. The same
|
|
430
|
+
fields, named two ways, on one screen, and in English inside an otherwise German
|
|
431
|
+
interface.
|
|
432
|
+
|
|
433
|
+
The banner asks the node's manifest now, which is where those names already live and
|
|
434
|
+
what the settings form has always rendered. Where there is no name to resolve — a
|
|
435
|
+
package that is gone, or a workflow older than the field it was saved with — the key
|
|
436
|
+
still stands: worse to read, but never the wrong field.
|
|
437
|
+
|
|
438
|
+
The badge on the card — the amber count whose hover lists the settings at fault — says
|
|
439
|
+
the same, and resolves it where it is drawn rather than upstream: the node card already
|
|
440
|
+
watches the manifest catalogue, so a name that arrives late redraws that one card instead
|
|
441
|
+
of re-syncing the whole canvas.
|
|
442
|
+
|
|
443
|
+
The marker inside the node's own settings form is deliberately unchanged. It is keyed by
|
|
444
|
+
the config key, which is how a message finds its input; a translated key would stop
|
|
445
|
+
matching the field it belongs to.
|
|
446
|
+
|
|
447
|
+
- d5838d6: The result of a run started by hand can be read when it is long.
|
|
448
|
+
|
|
449
|
+
The dialog that shows what a run returned capped its width and not its height, so it
|
|
450
|
+
grew as tall as its content: a result of two node outputs came out half a screen taller
|
|
451
|
+
than the screen, ran off the top and the bottom at once, and what stuck out was clipped
|
|
452
|
+
rather than made reachable — including the buttons at its foot, so the only way out was
|
|
453
|
+
the corner cross.
|
|
454
|
+
|
|
455
|
+
The body of the dialog now scrolls, as it already does in the create-workflow,
|
|
456
|
+
node-configuration and SFTP-import dialogs, and a single node's output may take a little
|
|
457
|
+
less of the screen so that two of them plus the footer stay navigable. Nothing about
|
|
458
|
+
what is shown changes.
|
|
459
|
+
|
|
460
|
+
Measured rather than eyeballed: with two outputs of a hundred entries each, the dialog
|
|
461
|
+
came out at 1106 pixels in a 900-pixel window before, and inside the window after.
|
|
462
|
+
|
|
463
|
+
- ea5b275: Picking the same node from the palette again lands it beside the last one instead of on top of it.
|
|
464
|
+
|
|
465
|
+
Clicking a row in the editor's node palette dropped the card in the centre of the view,
|
|
466
|
+
and the centre of the view does not move between two clicks. So five picks in a row
|
|
467
|
+
produced five nodes on one set of coordinates and one visible card — the workflow had
|
|
468
|
+
grown, the canvas said it had not, and the only way to find out was to drag the top card
|
|
469
|
+
away and see the next one underneath it.
|
|
470
|
+
|
|
471
|
+
A click-insert now steps off a spot the canvas already occupies, down and to the right in
|
|
472
|
+
grid steps, until it finds one that is clear. It reads the canvas each time rather than
|
|
473
|
+
counting the clicks so far, which is what makes a spot freed by dragging a card away
|
|
474
|
+
usable again — the next pick goes back to the centre of the view. The empty group frame
|
|
475
|
+
from the control pane, which is centred the same way and stacked the same way, goes
|
|
476
|
+
through the same helper.
|
|
477
|
+
|
|
478
|
+
Notes are deliberately left as they are. A note is drawn behind the cards because it
|
|
479
|
+
belongs over the nodes it explains, so a rule that keeps it clear of everything on the
|
|
480
|
+
canvas would push the first note of a workflow away from the middle of the view — two
|
|
481
|
+
notes in a row still land on one spot, and `specs/sticky-notes.md` says so.
|
|
482
|
+
|
|
483
|
+
Dragging a node out of the palette is deliberately unchanged: that gesture already shows a
|
|
484
|
+
ghost at the exact spot the card will land, so nudging the drop would make the ghost lie.
|
|
485
|
+
|
|
3
486
|
## 1.1.1
|
|
4
487
|
|
|
5
488
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -92,7 +92,9 @@ pnpm changeset # record what your change does to the version
|
|
|
92
92
|
|
|
93
93
|
Branch off `main` as `feature/<desc>`, `fix/<desc>` or `chore/<desc>` — GitHub refuses
|
|
94
94
|
the first push of any other name, and `main` itself takes a reviewed pull request with
|
|
95
|
-
CI green: [`docs/branch-protection.md`](docs/branch-protection.md).
|
|
95
|
+
CI green: [`docs/branch-protection.md`](docs/branch-protection.md). A separate,
|
|
96
|
+
non-blocking `Security Scan` reports secrets and dependency advisories:
|
|
97
|
+
[`docs/security-scanning.md`](docs/security-scanning.md).
|
|
96
98
|
|
|
97
99
|
Hosts to develop against:
|
|
98
100
|
|
package/dist/module.json
CHANGED
|
@@ -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
|
}
|