windmill-components 1.695.0 → 1.698.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 (157) hide show
  1. package/dist/sharedUtils/assets/tokens/colorTokensConfig.d.ts +2 -0
  2. package/dist/sharedUtils/base.d.ts +1 -0
  3. package/dist/sharedUtils/cloud.d.ts +1 -0
  4. package/dist/sharedUtils/common.d.ts +111 -0
  5. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/count.d.ts +5 -0
  6. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/delete.d.ts +5 -0
  7. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/insert.d.ts +5 -0
  8. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +13 -0
  9. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/update.d.ts +11 -0
  10. package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +95 -0
  11. package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +6 -0
  12. package/dist/sharedUtils/components/apps/editor/appUtilsCore.d.ts +7 -0
  13. package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +33 -0
  14. package/dist/sharedUtils/components/apps/editor/commonAppUtils.d.ts +10 -0
  15. package/dist/sharedUtils/components/apps/editor/component/components.d.ts +5371 -0
  16. package/dist/sharedUtils/components/apps/editor/component/default-codes.d.ts +3 -0
  17. package/dist/sharedUtils/components/apps/editor/component/index.d.ts +3 -0
  18. package/dist/sharedUtils/components/apps/editor/component/sets.d.ts +7 -0
  19. package/dist/sharedUtils/components/apps/editor/componentsPanel/componentDefaultProps.d.ts +3 -0
  20. package/dist/sharedUtils/components/apps/gridUtils.d.ts +14 -0
  21. package/dist/sharedUtils/components/apps/inputType.d.ts +178 -0
  22. package/dist/sharedUtils/components/apps/rx.d.ts +29 -0
  23. package/dist/sharedUtils/components/apps/sharedTypes.d.ts +21 -0
  24. package/dist/sharedUtils/components/apps/types.d.ts +274 -0
  25. package/dist/sharedUtils/components/assets/lib.d.ts +25 -0
  26. package/dist/sharedUtils/components/common/alert/model.d.ts +2 -0
  27. package/dist/sharedUtils/components/common/badge/model.d.ts +8 -0
  28. package/dist/sharedUtils/components/common/button/model.d.ts +45 -0
  29. package/dist/sharedUtils/components/common/fileInput/model.d.ts +1 -0
  30. package/dist/sharedUtils/components/common/index.d.ts +24 -0
  31. package/dist/sharedUtils/components/common/skeleton/model.d.ts +21 -0
  32. package/dist/sharedUtils/components/dbTypes.d.ts +14 -0
  33. package/dist/sharedUtils/components/diff_drawer.d.ts +26 -0
  34. package/dist/sharedUtils/components/ducklake.d.ts +1 -0
  35. package/dist/sharedUtils/components/flows/scheduleUtils.d.ts +7 -0
  36. package/dist/sharedUtils/components/icons/index.d.ts +101 -0
  37. package/dist/sharedUtils/components/random_positive_adjetive.d.ts +1 -0
  38. package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +10 -0
  39. package/dist/sharedUtils/components/raw_apps/utils.d.ts +15 -0
  40. package/dist/sharedUtils/components/triggers/email/utils.d.ts +4 -0
  41. package/dist/sharedUtils/components/triggers/gcp/utils.d.ts +2 -0
  42. package/dist/sharedUtils/components/triggers/http/utils.d.ts +11 -0
  43. package/dist/sharedUtils/components/triggers/kafka/utils.d.ts +2 -0
  44. package/dist/sharedUtils/components/triggers/mqtt/utils.d.ts +2 -0
  45. package/dist/sharedUtils/components/triggers/nats/utils.d.ts +2 -0
  46. package/dist/sharedUtils/components/triggers/postgres/utils.d.ts +8 -0
  47. package/dist/sharedUtils/components/triggers/sqs/utils.d.ts +2 -0
  48. package/dist/sharedUtils/components/triggers/triggers.svelte.d.ts +32 -0
  49. package/dist/sharedUtils/components/triggers/utils.d.ts +80 -0
  50. package/dist/sharedUtils/components/triggers/websocket/utils.d.ts +2 -0
  51. package/dist/sharedUtils/components/triggers.d.ts +20 -0
  52. package/dist/sharedUtils/gen/core/ApiError.d.ts +10 -0
  53. package/dist/sharedUtils/gen/core/ApiRequestOptions.d.ts +13 -0
  54. package/dist/sharedUtils/gen/core/ApiResult.d.ts +7 -0
  55. package/dist/sharedUtils/gen/core/CancelablePromise.d.ts +26 -0
  56. package/dist/sharedUtils/gen/core/OpenAPI.d.ts +27 -0
  57. package/dist/sharedUtils/gen/core/request.d.ts +29 -0
  58. package/dist/sharedUtils/gen/index.d.ts +6 -0
  59. package/dist/sharedUtils/gen/schemas.gen.d.ts +7036 -0
  60. package/dist/sharedUtils/gen/services.gen.d.ts +6047 -0
  61. package/dist/sharedUtils/gen/types.gen.d.ts +21881 -0
  62. package/dist/sharedUtils/history.svelte.d.ts +9 -0
  63. package/dist/sharedUtils/hub.d.ts +49 -0
  64. package/dist/sharedUtils/jsr.json +6 -0
  65. package/dist/sharedUtils/lib.d.ts +5 -0
  66. package/dist/sharedUtils/lib.es.js +1588 -0
  67. package/dist/sharedUtils/package.json +12 -0
  68. package/dist/sharedUtils/schema.d.ts +3 -0
  69. package/dist/sharedUtils/stores.d.ts +97 -0
  70. package/dist/sharedUtils/svelte5Utils.svelte.d.ts +80 -0
  71. package/dist/sharedUtils/toast.d.ts +8 -0
  72. package/dist/sharedUtils/utils.d.ts +265 -0
  73. package/package/components/AppConnectInner.svelte +38 -5
  74. package/package/components/CompareWorkspaces.svelte +142 -486
  75. package/package/components/Editor.svelte +5 -4
  76. package/package/components/Editor.svelte.d.ts +1 -0
  77. package/package/components/FilterSearchbar.svelte +3 -1
  78. package/package/components/FilterSearchbar.svelte.d.ts +1 -0
  79. package/package/components/ForkWorkspaceBanner.svelte +16 -0
  80. package/package/components/LogViewer.svelte +51 -60
  81. package/package/components/OnBehalfOfSelector.svelte +10 -7
  82. package/package/components/ResourceEditor.svelte +198 -311
  83. package/package/components/ResourceEditor.svelte.d.ts +3 -3
  84. package/package/components/ResourceEditorDrawer.svelte +17 -6
  85. package/package/components/ResourceForm.svelte +235 -0
  86. package/package/components/ResourceForm.svelte.d.ts +25 -0
  87. package/package/components/RunsPage.svelte +1 -0
  88. package/package/components/ScriptBuilder.svelte +1 -0
  89. package/package/components/ScriptEditor.svelte +10 -3
  90. package/package/components/ScriptEditor.svelte.d.ts +1 -0
  91. package/package/components/TaggedTextInput.svelte +4 -1
  92. package/package/components/TaggedTextInput.svelte.d.ts +2 -0
  93. package/package/components/VariableEditor.svelte +177 -199
  94. package/package/components/VariableEditor.svelte.d.ts +1 -2
  95. package/package/components/VariableForm.svelte +133 -0
  96. package/package/components/VariableForm.svelte.d.ts +22 -0
  97. package/package/components/WsSpecificVersions.svelte +39 -0
  98. package/package/components/WsSpecificVersions.svelte.d.ts +9 -0
  99. package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +0 -1
  100. package/package/components/apps/editor/AppEditorHeaderDeploy.svelte.d.ts +1 -1
  101. package/package/components/common/table/AppRow.svelte +2 -1
  102. package/package/components/common/table/AppRow.svelte.d.ts +1 -0
  103. package/package/components/common/table/FlowRow.svelte +2 -1
  104. package/package/components/common/table/FlowRow.svelte.d.ts +1 -0
  105. package/package/components/common/table/RawAppRow.svelte +2 -1
  106. package/package/components/common/table/RawAppRow.svelte.d.ts +1 -0
  107. package/package/components/common/table/Row.svelte +11 -3
  108. package/package/components/common/table/Row.svelte.d.ts +2 -1
  109. package/package/components/common/table/RowIcon.svelte +18 -2
  110. package/package/components/common/table/RowIcon.svelte.d.ts +1 -1
  111. package/package/components/common/table/ScriptRow.svelte +2 -1
  112. package/package/components/common/table/ScriptRow.svelte.d.ts +1 -0
  113. package/package/components/copilot/autocomplete/Autocompletor.d.ts +3 -1
  114. package/package/components/copilot/autocomplete/Autocompletor.js +5 -2
  115. package/package/components/copilot/autocomplete/request.d.ts +1 -0
  116. package/package/components/copilot/autocomplete/request.js +1 -1
  117. package/package/components/copilot/chat/AIChatManager.svelte.js +14 -4
  118. package/package/components/copilot/chat/AiChatLayout.svelte +2 -0
  119. package/package/components/copilot/chat/ContextManager.svelte.d.ts +1 -0
  120. package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte +129 -0
  121. package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte.d.ts +4 -0
  122. package/package/components/copilot/chat/ToolExecutionDisplay.svelte +14 -6
  123. package/package/components/copilot/chat/ToolMessageActions.svelte +73 -0
  124. package/package/components/copilot/chat/ToolMessageActions.svelte.d.ts +7 -0
  125. package/package/components/copilot/chat/createdResourceActions.svelte.d.ts +6 -0
  126. package/package/components/copilot/chat/createdResourceActions.svelte.js +29 -0
  127. package/package/components/copilot/chat/script/core.d.ts +6 -2
  128. package/package/components/copilot/chat/script/core.js +13 -7
  129. package/package/components/copilot/chat/script/wacPrompt.test.d.ts +1 -0
  130. package/package/components/copilot/chat/script/wacPrompt.test.js +25 -0
  131. package/package/components/copilot/chat/shared.d.ts +12 -0
  132. package/package/components/copilot/chat/shared.test.js +23 -2
  133. package/package/components/copilot/chat/workspaceTools.js +34 -4
  134. package/package/components/flows/content/ScriptEditorDrawer.svelte +1 -0
  135. package/package/components/graph/wacToFlow.js +1 -1
  136. package/package/components/graph/wacToFlow.test.d.ts +1 -0
  137. package/package/components/graph/wacToFlow.test.js +17 -0
  138. package/package/components/home/Item.svelte +5 -1
  139. package/package/components/home/Item.svelte.d.ts +1 -0
  140. package/package/components/home/ItemsList.svelte +260 -3
  141. package/package/components/instanceSettings/SecretBackendConfig.svelte +457 -88
  142. package/package/components/runs/useJobsLoader.svelte.js +5 -11
  143. package/package/components/sidebar/WorkspaceMenu.svelte +19 -5
  144. package/package/externalDomain.d.ts +2 -0
  145. package/package/externalDomain.js +16 -0
  146. package/package/gen/core/OpenAPI.js +1 -1
  147. package/package/gen/types.gen.d.ts +0 -112
  148. package/package/hubPaths.json +2 -2
  149. package/package/system_prompts/index.d.ts +1 -1
  150. package/package/system_prompts/index.js +22 -3
  151. package/package/system_prompts/prompts.d.ts +2 -2
  152. package/package/system_prompts/prompts.js +6 -3
  153. package/package/utils_deployable.d.ts +162 -638
  154. package/package/utils_deployable.js +75 -143
  155. package/package/utils_workspace_deploy.d.ts +10 -4
  156. package/package/utils_workspace_deploy.js +167 -42
  157. package/package.json +7 -3
@@ -1,24 +1,19 @@
1
1
  <script lang="ts">import { AlertTriangle, ArrowDown, ArrowDownRight, ArrowRight, ArrowUp, ArrowUpRight, Building, CircleCheck, CircleX, DiffIcon, FileJson, FlaskConical, GitFork, Loader2, UserPlus } from 'lucide-svelte';
2
2
  import { Alert, Badge } from './common';
3
- import { AppService, EmailTriggerService, FlowService, FolderService, AzureTriggerService, GcpTriggerService, HttpTriggerService, KafkaTriggerService, MqttTriggerService, NatsTriggerService, PostgresTriggerService, ScheduleService, ScriptService, SqsTriggerService, UserService, WebsocketTriggerService, WorkspaceService } from '../gen';
3
+ import { AppService, FlowService, FolderService, ScriptService, UserService, WorkspaceService } from '../gen';
4
4
  import Button from './common/button/Button.svelte';
5
5
  import DiffDrawer from './DiffDrawer.svelte';
6
- import DiffEditor from './DiffEditor.svelte';
7
- import Drawer from './common/drawer/Drawer.svelte';
8
- import DrawerContent from './common/drawer/DrawerContent.svelte';
9
6
  import ParentWorkspaceProtectionAlert from './ParentWorkspaceProtectionAlert.svelte';
10
7
  import { userWorkspaces, workspaceStore } from '../stores';
11
8
  import { deployItem, deleteItemInWorkspace, getItemValue, getOnBehalfOf } from '../utils_workspace_deploy';
9
+ import { isTriggerOrScheduleKind } from 'windmill-utils-internal';
12
10
  import Tooltip from './Tooltip.svelte';
13
11
  import OnBehalfOfSelector, { needsOnBehalfOfSelection } from './OnBehalfOfSelector.svelte';
14
12
  import { sendUserToast } from '../toast';
15
13
  import { deepEqual } from 'fast-equals';
16
- import { orderedJsonStringify, orderedYamlStringify } from '../utils';
17
14
  import WorkspaceDeployLayout from './WorkspaceDeployLayout.svelte';
18
15
  import DeploymentRequestPanel from './deploymentRequest/DeploymentRequestPanel.svelte';
19
16
  import { userStore } from '../stores';
20
- import { triggerDisplayNamesMap, triggerKindToTriggerType } from './triggers/utils';
21
- import { getEmailAddress, getEmailDomain } from './triggers/email/utils';
22
17
  import { base } from '../base';
23
18
  import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte';
24
19
  import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte';
@@ -110,8 +105,11 @@ async function fetchSummaries(diffs) {
110
105
  }
111
106
  }
112
107
  async function fetchOnBehalfOfInfo(diffs) {
113
- const flowsAndScripts = diffs.filter((d) => ['flow', 'script', 'app', 'raw_app'].includes(d.kind));
114
- for (const diff of flowsAndScripts) {
108
+ // Runnables carry an email; triggers/schedules carry `permissioned_as`.
109
+ // `getOnBehalfOf` handles the dispatch — we just need to skip kinds that
110
+ // have no on_behalf_of concept (resource, variable, folder, resource_type).
111
+ const itemsWithOnBehalfOf = diffs.filter((d) => ['flow', 'script', 'app', 'raw_app'].includes(d.kind) || isTriggerOrScheduleKind(d.kind));
112
+ for (const diff of itemsWithOnBehalfOf) {
115
113
  for (const workspace of [currentWorkspaceId, parentWorkspaceId]) {
116
114
  const workspacedKey = getWorkspacedKey(workspace, getItemKey(diff));
117
115
  if (onBehalfOfInfo[workspacedKey] !== undefined)
@@ -156,7 +154,8 @@ function getOnBehalfOfForDeploy(itemKey, kind) {
156
154
  return getTargetOnBehalfOf(itemKey);
157
155
  if (choice === 'custom') {
158
156
  const details = customOnBehalfOf[itemKey];
159
- return kind === 'trigger' ? details?.permissionedAs : details?.email;
157
+ const wantsPermissionedAs = kind === 'trigger' || isTriggerOrScheduleKind(kind);
158
+ return wantsPermissionedAs ? details?.permissionedAs : details?.email;
160
159
  }
161
160
  // 'me' or undefined = don't pass, backend will use deploying user's identity
162
161
  return undefined;
@@ -193,17 +192,11 @@ async function selectAll() {
193
192
  function deselectAll() {
194
193
  selectedItems = [];
195
194
  }
196
- async function selectAllNonConflicts() {
197
- selectedItems = selectableDiffs
198
- .filter((d) => !(d.ahead > 0 && d.behind > 0))
199
- .map((d) => getItemKey(d))
200
- .filter((k) => !(deploymentStatus[k]?.status == 'deployed'));
201
- }
202
195
  const deploymentStatus = $state({});
203
196
  function getWorkspacedKey(workspace, key) {
204
197
  return `${workspace}/${key}`;
205
198
  }
206
- async function deploy(kind, path, workspaceToDeployTo, workspaceFrom, statusKey, trigger) {
199
+ async function deploy(kind, path, workspaceToDeployTo, workspaceFrom, statusKey) {
207
200
  deploymentStatus[statusKey] = { status: 'loading' };
208
201
  // Check if the item was deleted in the source workspace.
209
202
  // If so, archive/delete it in the target workspace instead of copying.
@@ -217,16 +210,6 @@ async function deploy(kind, path, workspaceToDeployTo, workspaceFrom, statusKey,
217
210
  if (itemDeletedInSource) {
218
211
  result = await deleteItemInWorkspace(kind, path, workspaceToDeployTo);
219
212
  }
220
- else if (trigger) {
221
- result = await deployItem({
222
- kind: 'trigger',
223
- path,
224
- workspaceFrom,
225
- workspaceTo: workspaceToDeployTo,
226
- additionalInformation: { triggers: { kind: trigger.triggerKind } },
227
- onBehalfOf: getOnBehalfOfForDeploy(statusKey, 'trigger')
228
- });
229
- }
230
213
  else {
231
214
  result = await deployItem({
232
215
  kind,
@@ -294,7 +277,7 @@ async function deployChanges() {
294
277
  }
295
278
  const to = mergeIntoParent ? parent : current;
296
279
  const from = mergeIntoParent ? current : parent;
297
- await deploy(deployable.kind, deployable.path, to, from, itemKey, deployable.trigger);
280
+ await deploy(deployable.kind, deployable.path, to, from, itemKey);
298
281
  if (deploymentStatus[itemKey]?.status === 'failed') {
299
282
  anyFailed = true;
300
283
  }
@@ -330,12 +313,17 @@ function toggleKey(key) {
330
313
  }
331
314
  }
332
315
  function selectDefault() {
333
- if (mergeIntoParent) {
334
- selectAll();
335
- }
336
- else {
337
- selectAllNonConflicts();
338
- }
316
+ // Triggers and schedules are opt-in: they often share runtime state with the
317
+ // parent (kafka group_id, postgres replication slot, schedule firing time)
318
+ // and pushing them by default would surprise users running a routine "Deploy
319
+ // to parent" flow. The user picks them à la carte by clicking the row.
320
+ const filtered = selectableDiffs.filter((d) => !isTriggerOrScheduleKind(d.kind));
321
+ const conflictSafe = mergeIntoParent
322
+ ? filtered
323
+ : filtered.filter((d) => !(d.ahead > 0 && d.behind > 0));
324
+ selectedItems = conflictSafe
325
+ .map((d) => getItemKey(d))
326
+ .filter((k) => !(deploymentStatus[k]?.status == 'deployed'));
339
327
  }
340
328
  function toggleDeploymentDirection(v) {
341
329
  deselectAll();
@@ -386,14 +374,11 @@ $effect(() => {
386
374
  [selectedItems, mergeIntoParent];
387
375
  allowBehindChangesOverride = false;
388
376
  });
389
- function getTriggerKey(trigger) {
390
- return `trigger:${trigger.triggerKind}:${trigger.path}`;
391
- }
392
- // Transform diffs + fork triggers to deployable item format for the
393
- // shared layout. Triggers render as inline rows alongside diff items;
394
- // they carry no ahead/behind info and are selectable à la carte.
377
+ // Trigger and schedule rows now flow through `comparison.diffs` like every
378
+ // other deployable kind — the backend's `compareWorkspaces` populates them
379
+ // from `workspace_diff`, with runtime fields ignored by `compare_two_*`.
395
380
  let deployableItems = $derived.by(() => {
396
- const diffItems = (comparison?.diffs ?? [])
381
+ return (comparison?.diffs ?? [])
397
382
  .filter((diff) => {
398
383
  const key = getItemKey(diff);
399
384
  const isSelectable = selectableDiffs.includes(diff);
@@ -404,25 +389,8 @@ let deployableItems = $derived.by(() => {
404
389
  key: getItemKey(diff),
405
390
  path: diff.path,
406
391
  kind: diff.kind,
407
- diff,
408
- trigger: undefined
392
+ diff
409
393
  }));
410
- const triggerItems = forkTriggers
411
- .filter((t) => {
412
- if (!isTriggerRelevantForDirection(t, mergeIntoParent))
413
- return false;
414
- const key = getTriggerKey(t);
415
- return deploymentStatus[key]?.status !== 'deployed';
416
- })
417
- .map((trigger) => ({
418
- key: getTriggerKey(trigger),
419
- path: trigger.path,
420
- kind: 'trigger',
421
- triggerKind: trigger.triggerKind,
422
- diff: undefined,
423
- trigger
424
- }));
425
- return [...diffItems, ...triggerItems];
426
394
  });
427
395
  let ciTestResults = $state({});
428
396
  async function fetchCiTests() {
@@ -475,268 +443,22 @@ let allCiTests = $derived.by(() => {
475
443
  }
476
444
  return [...seen.values()];
477
445
  });
478
- let forkTriggers = $state([]);
479
446
  let deploymentRequestPanel = $state(undefined);
480
447
  let hasOpenDeploymentRequest = $state(false);
481
- /** Deployable trigger kinds and their list services */
482
- const triggerServices = {
483
- schedules: {
484
- list: (ws) => ScheduleService.listSchedules({ workspace: ws }),
485
- normalize: (item) => ({
486
- path: item.path,
487
- triggerKind: 'schedules',
488
- scriptPath: item.script_path,
489
- isFlow: item.is_flow,
490
- extraLabel: item.schedule,
491
- raw: item
492
- })
493
- },
494
- routes: {
495
- list: (ws) => HttpTriggerService.listHttpTriggers({ workspace: ws }),
496
- normalize: (item) => ({
497
- path: item.path,
498
- triggerKind: 'routes',
499
- scriptPath: item.script_path,
500
- isFlow: item.is_flow,
501
- extraLabel: `${(item.http_method ?? 'get').toUpperCase()} ${item.route_path ?? ''}`
502
- })
503
- },
504
- websockets: {
505
- list: (ws) => WebsocketTriggerService.listWebsocketTriggers({ workspace: ws }),
506
- normalize: (item) => ({
507
- path: item.path,
508
- triggerKind: 'websockets',
509
- scriptPath: item.script_path,
510
- isFlow: item.is_flow,
511
- extraLabel: item.url,
512
- raw: item
513
- })
514
- },
515
- kafka: {
516
- list: (ws) => KafkaTriggerService.listKafkaTriggers({ workspace: ws }),
517
- normalize: (item) => ({
518
- path: item.path,
519
- triggerKind: 'kafka',
520
- scriptPath: item.script_path,
521
- isFlow: item.is_flow,
522
- extraLabel: item.topics?.join(', '),
523
- raw: item
524
- })
525
- },
526
- postgres: {
527
- list: (ws) => PostgresTriggerService.listPostgresTriggers({ workspace: ws }),
528
- normalize: (item) => ({
529
- path: item.path,
530
- triggerKind: 'postgres',
531
- scriptPath: item.script_path,
532
- isFlow: item.is_flow,
533
- raw: item
534
- })
535
- },
536
- nats: {
537
- list: (ws) => NatsTriggerService.listNatsTriggers({ workspace: ws }),
538
- normalize: (item) => ({
539
- path: item.path,
540
- triggerKind: 'nats',
541
- scriptPath: item.script_path,
542
- isFlow: item.is_flow,
543
- extraLabel: item.subjects?.join(', '),
544
- raw: item
545
- })
546
- },
547
- mqtt: {
548
- list: (ws) => MqttTriggerService.listMqttTriggers({ workspace: ws }),
549
- normalize: (item) => ({
550
- path: item.path,
551
- triggerKind: 'mqtt',
552
- scriptPath: item.script_path,
553
- isFlow: item.is_flow,
554
- raw: item
555
- })
556
- },
557
- sqs: {
558
- list: (ws) => SqsTriggerService.listSqsTriggers({ workspace: ws }),
559
- normalize: (item) => ({
560
- path: item.path,
561
- triggerKind: 'sqs',
562
- scriptPath: item.script_path,
563
- isFlow: item.is_flow,
564
- extraLabel: item.queue_url,
565
- raw: item
566
- })
567
- },
568
- gcp: {
569
- list: (ws) => GcpTriggerService.listGcpTriggers({ workspace: ws }),
570
- normalize: (item) => ({
571
- path: item.path,
572
- triggerKind: 'gcp',
573
- scriptPath: item.script_path,
574
- isFlow: item.is_flow,
575
- extraLabel: item.topic_id,
576
- raw: item
577
- })
578
- },
579
- azure: {
580
- list: (ws) => AzureTriggerService.listAzureTriggers({ workspace: ws }),
581
- normalize: (item) => ({
582
- path: item.path,
583
- triggerKind: 'azure',
584
- scriptPath: item.script_path,
585
- isFlow: item.is_flow,
586
- extraLabel: item.topic_name ?? item.scope_resource_id,
587
- raw: item
588
- })
589
- },
590
- emails: {
591
- list: (ws) => EmailTriggerService.listEmailTriggers({ workspace: ws }),
592
- normalize: (item) => ({
593
- path: item.path,
594
- triggerKind: 'emails',
595
- scriptPath: item.script_path,
596
- isFlow: item.is_flow,
597
- extraLabel: getEmailAddress(item.local_part, item.workspaced_local_part, currentWorkspaceId, emailDomain ?? ''),
598
- raw: item
599
- })
600
- }
448
+ /** Display labels for trigger/schedule kinds in the merge UI. */
449
+ const KIND_DISPLAY_NAMES = {
450
+ schedule: 'Schedule',
451
+ http_trigger: 'HTTP route',
452
+ websocket_trigger: 'Websocket trigger',
453
+ kafka_trigger: 'Kafka trigger',
454
+ nats_trigger: 'NATS trigger',
455
+ postgres_trigger: 'Postgres trigger',
456
+ mqtt_trigger: 'MQTT trigger',
457
+ sqs_trigger: 'SQS trigger',
458
+ gcp_trigger: 'GCP trigger',
459
+ azure_trigger: 'Azure trigger',
460
+ email_trigger: 'Email trigger'
601
461
  };
602
- let emailDomain = $state(undefined);
603
- /**
604
- * Fields that should not count as a "change" between the parent and the
605
- * fork. Mode/enabled are forced to 'disabled'/false on clone and stripped
606
- * by the fork-export filter; the rest are runtime state or per-row
607
- * metadata that always diverges. Comparing without these matches the
608
- * semantics of "is this trigger configured the same way?" rather than
609
- * "are these two rows byte-identical?".
610
- */
611
- const TRIGGER_COMPARE_IGNORE = new Set([
612
- 'workspace_id',
613
- 'mode',
614
- 'enabled',
615
- 'edited_at',
616
- 'edited_by',
617
- 'last_server_ping',
618
- 'server_id',
619
- 'error',
620
- 'extra_perms',
621
- 'permissioned_as'
622
- ]);
623
- function stripIgnoredFields(row) {
624
- if (!row || typeof row !== 'object')
625
- return row;
626
- const out = {};
627
- for (const [k, v] of Object.entries(row)) {
628
- if (!TRIGGER_COMPARE_IGNORE.has(k))
629
- out[k] = v;
630
- }
631
- return out;
632
- }
633
- function rowsHaveSameConfig(a, b) {
634
- return (orderedJsonStringify(stripIgnoredFields(a)) === orderedJsonStringify(stripIgnoredFields(b)));
635
- }
636
- async function fetchAllTriggers() {
637
- try {
638
- emailDomain = await getEmailDomain();
639
- const entries = Object.entries(triggerServices);
640
- // Fetch fork + parent in parallel for each kind. Either side may
641
- // fail (e.g. permission denied on parent) — fall back to empty.
642
- const results = await Promise.allSettled(entries.map(async ([kind, svc]) => {
643
- const [forkItems, parentItems] = await Promise.all([
644
- svc.list(currentWorkspaceId).catch(() => []),
645
- svc.list(parentWorkspaceId).catch(() => [])
646
- ]);
647
- const byPath = new Map();
648
- for (const item of forkItems) {
649
- byPath.set(item.path, { fork: item });
650
- }
651
- for (const item of parentItems) {
652
- const entry = byPath.get(item.path) ?? {};
653
- entry.parent = item;
654
- byPath.set(item.path, entry);
655
- }
656
- const merged = [];
657
- for (const [path, entry] of byPath) {
658
- const sourceItem = entry.fork ?? entry.parent;
659
- const normalized = svc.normalize(sourceItem);
660
- let changeKind;
661
- let hasChanges = false;
662
- if (entry.fork && !entry.parent) {
663
- changeKind = 'new';
664
- }
665
- else if (!entry.fork && entry.parent) {
666
- changeKind = 'deleted-in-source';
667
- }
668
- else if (entry.fork && entry.parent) {
669
- hasChanges = !rowsHaveSameConfig(entry.fork, entry.parent);
670
- if (hasChanges)
671
- changeKind = 'modified';
672
- }
673
- merged.push({
674
- ...normalized,
675
- raw: sourceItem,
676
- hasChanges,
677
- changeKind,
678
- sourceRaw: entry.fork,
679
- targetRaw: entry.parent,
680
- path
681
- });
682
- }
683
- return merged;
684
- }));
685
- forkTriggers = results.flatMap((r) => (r.status === 'fulfilled' ? r.value : []));
686
- }
687
- catch (e) {
688
- console.error('Failed to fetch fork triggers:', e);
689
- forkTriggers = [];
690
- }
691
- }
692
- /**
693
- * Triggers worth showing in the merge UI given the current direction.
694
- * - "Deploy to parent": rows that exist in fork and either don't exist in
695
- * parent or have config differences.
696
- * - "Update current" (pull from parent): mirror.
697
- * Triggers that exist on both sides with identical config are filtered
698
- * out — they would generate a no-op deploy and only add noise.
699
- */
700
- function isTriggerRelevantForDirection(t, deployingToParent) {
701
- const existsInFork = !!t.sourceRaw;
702
- const existsInParent = !!t.targetRaw;
703
- if (deployingToParent) {
704
- return existsInFork && (!existsInParent || !!t.hasChanges);
705
- }
706
- else {
707
- return existsInParent && (!existsInFork || !!t.hasChanges);
708
- }
709
- }
710
- function getTriggerDisplayName(triggerKind) {
711
- const triggerType = triggerKindToTriggerType(triggerKind);
712
- return triggerType ? triggerDisplayNamesMap[triggerType] : triggerKind;
713
- }
714
- let triggerDiffOpen = $state(false);
715
- let triggerDiffPayload = $state(undefined);
716
- function openTriggerDiff(t) {
717
- // `sourceRaw` is the fork row, `targetRaw` is the parent row regardless
718
- // of direction (set in fetchAllTriggers). The diff reads from the
719
- // destination (left) to the source (right), matching the deploy arrow.
720
- const sourceWorkspace = mergeIntoParent ? currentWorkspaceId : parentWorkspaceId;
721
- const targetWorkspace = mergeIntoParent ? parentWorkspaceId : currentWorkspaceId;
722
- const fromRow = mergeIntoParent ? t.sourceRaw : t.targetRaw;
723
- const toRow = mergeIntoParent ? t.targetRaw : t.sourceRaw;
724
- triggerDiffPayload = {
725
- kindLabel: getTriggerDisplayName(t.triggerKind),
726
- path: t.path,
727
- originalLabel: `${targetWorkspace} (target)`,
728
- modifiedLabel: `${sourceWorkspace} (source)`,
729
- original: orderedYamlStringify(stripIgnoredFields(toRow ?? {})),
730
- modified: orderedYamlStringify(stripIgnoredFields(fromRow ?? {}))
731
- };
732
- triggerDiffOpen = true;
733
- }
734
- // Fetch triggers when workspace is available
735
- $effect(() => {
736
- if (currentWorkspaceId) {
737
- fetchAllTriggers();
738
- }
739
- });
740
462
  </script>
741
463
 
742
464
  {#if $workspaceStore != currentWorkspaceId}
@@ -760,8 +482,7 @@ $effect(() => {
760
482
  items={deployableItems}
761
483
  {selectedItems}
762
484
  {deploymentStatus}
763
- selectablePredicate={(item) =>
764
- item.trigger != null || selectableDiffs.some((d) => getItemKey(d) === item.key)}
485
+ selectablePredicate={(item) => selectableDiffs.some((d) => getItemKey(d) === item.key)}
765
486
  {allSelected}
766
487
  onToggleItem={(item) => toggleKey(item.key)}
767
488
  onSelectAll={selectAll}
@@ -935,17 +656,15 @@ $effect(() => {
935
656
  {/snippet}
936
657
 
937
658
  {#snippet itemSummary(item)}
938
- {#if item.trigger}
939
- {@const t = item.trigger as ForkTrigger}
940
- <span class="text-emphasis">{getTriggerDisplayName(t.triggerKind)}</span>
941
- {#if t.extraLabel}
942
- <span class="text-secondary ml-1">{t.extraLabel}</span>
943
- {/if}
659
+ {@const diff = item.diff as WorkspaceItemDiff}
660
+ {@const key = item.key}
661
+ {#if isTriggerOrScheduleKind(diff.kind)}
662
+ <span class="text-emphasis">
663
+ {KIND_DISPLAY_NAMES[diff.kind as string] ?? diff.kind}
664
+ </span>
944
665
  <span class="text-tertiary mx-1">&rarr;</span>
945
- <span class="text-secondary">{t.scriptPath}</span>
666
+ <span class="text-secondary">{diff.path}</span>
946
667
  {:else}
947
- {@const diff = item.diff as WorkspaceItemDiff}
948
- {@const key = item.key}
949
668
  {@const isSelectable = selectableDiffs.includes(diff)}
950
669
  {@const oldSummary = mergeIntoParent
951
670
  ? summaryCache[key]?.parent
@@ -969,140 +688,102 @@ $effect(() => {
969
688
  {/snippet}
970
689
 
971
690
  {#snippet itemActions(item)}
972
- {#if item.trigger}
973
- {@const t = item.trigger as ForkTrigger}
974
- {@const key = item.key}
975
- {#if t.changeKind === 'new'}
976
- <Badge
977
- title={mergeIntoParent
978
- ? `Only exists in '${currentWorkspaceId}'`
979
- : `Only exists in '${parentWorkspaceId}'`}
980
- color="indigo"
981
- size="xs">New</Badge
982
- >
983
- {/if}
984
- {#if t.isFlow}
985
- <Badge color="blue" size="xs">flow</Badge>
986
- {/if}
987
- {#if !deploymentStatus[key] || deploymentStatus[key].status != 'deployed'}
988
- {#if mergeIntoParent}
989
- <Badge color="green" size="xs">
990
- <ArrowUpRight class="w-3 h-3 inline" />
991
- 1 ahead
992
- </Badge>
993
- {:else}
994
- <Badge color="blue" size="xs">
995
- <ArrowDownRight class="w-3 h-3 inline" />
996
- 1 behind
997
- </Badge>
998
- {/if}
999
- {#if t.changeKind === 'modified'}
1000
- <div>
1001
- <Button size="xs" variant="subtle" onclick={() => openTriggerDiff(t)}>
1002
- <DiffIcon class="w-3 h-3" />
1003
- Show diff
1004
- </Button>
1005
- </div>
691
+ {@const diff = item.diff as WorkspaceItemDiff}
692
+ {@const key = item.key}
693
+ {@const targetOnBehalfOf = getTargetOnBehalfOf(key)}
694
+ {@const isConflict = diff.ahead > 0 && diff.behind > 0}
695
+ {@const existsInBothWorkspaces = !(
696
+ (diff.exists_in_fork && !diff.exists_in_source) ||
697
+ (!diff.exists_in_fork && diff.exists_in_source)
698
+ )}
699
+ <!-- On-behalf-of selector -->
700
+ {#if itemNeedsOnBehalfOfSelection(key, diff.kind)}
701
+ <OnBehalfOfSelector
702
+ targetWorkspace={deployTargetWorkspace}
703
+ targetValue={targetOnBehalfOf}
704
+ selected={onBehalfOfChoice[key]}
705
+ onSelect={(choice, details) => {
706
+ onBehalfOfChoice[key] = choice
707
+ if (details) customOnBehalfOf[key] = details
708
+ }}
709
+ kind={diff.kind}
710
+ canPreserve={canPreserveOnBehalfOf}
711
+ customValue={customOnBehalfOf[key]?.permissionedAs}
712
+ />
713
+ {/if}
714
+ {#if diff.kind === 'raw_app'}
715
+ <Badge small icon={{ icon: FileJson }}>Raw</Badge>
716
+ {/if}
717
+ <!-- Status badges -->
718
+ {#if !diff.exists_in_fork && diff.exists_in_source && diff.ahead == 0 && diff.behind > 0}
719
+ <Badge
720
+ title="This item was newly created in the parent workspace '{parentWorkspaceId}'"
721
+ color="indigo"
722
+ size="xs">New</Badge
723
+ >
724
+ {/if}
725
+ {#if !diff.exists_in_fork && diff.exists_in_source && diff.ahead > 0}
726
+ <Badge title="This item was deleted in '{currentWorkspaceId}'" color="red" size="xs"
727
+ >Deleted</Badge
728
+ >
729
+ {/if}
730
+ {#if diff.exists_in_fork && !diff.exists_in_source && diff.behind > 0}
731
+ <Badge
732
+ title="This item was deleted in the parent workspace '{parentWorkspaceId}'"
733
+ color="red"
734
+ size="xs">Deleted</Badge
735
+ >
736
+ {/if}
737
+ {#if diff.exists_in_fork && !diff.exists_in_source && diff.ahead > 0 && diff.behind == 0}
738
+ <Badge
739
+ title="This item was newly created in '{currentWorkspaceId}'"
740
+ color="indigo"
741
+ size="xs">New</Badge
742
+ >
743
+ {/if}
744
+ {@const ciStatus = getCiTestStatus(diff)}
745
+ {#if ciStatus === 'pass'}
746
+ <Badge color="green" size="xs"><CircleCheck size={10} class="mr-0.5" />CI pass</Badge>
747
+ {:else if ciStatus === 'fail'}
748
+ <Badge color="red" size="xs"><CircleX size={10} class="mr-0.5" />CI fail</Badge>
749
+ {:else if ciStatus === 'running'}
750
+ <Badge color="yellow" size="xs"
751
+ ><Loader2 size={10} class="mr-0.5 animate-spin" />CI</Badge
752
+ >
753
+ {/if}
754
+ {#if !deploymentStatus[key] || deploymentStatus[key].status != 'deployed'}
755
+ <div class="flex items-center gap-2">
756
+ {#if isConflict || existsInBothWorkspaces}
757
+ {#if diff.ahead > 0}
758
+ <Badge color="green" size="xs">
759
+ <ArrowUpRight class="w-3 h-3 inline" />
760
+ {diff.ahead} ahead
761
+ </Badge>
762
+ {/if}
763
+ {#if diff.behind > 0}
764
+ <Badge color="blue" size="xs">
765
+ <ArrowDownRight class="w-3 h-3 inline" />
766
+ {diff.behind} behind
767
+ </Badge>
768
+ {/if}
769
+ {#if isConflict}
770
+ <Badge color="orange" size="xs">
771
+ <AlertTriangle class="w-3 h-3 inline" />
772
+ Conflict
773
+ </Badge>
774
+ {/if}
1006
775
  {/if}
1007
- {/if}
1008
- {:else}
1009
- {@const diff = item.diff as WorkspaceItemDiff}
1010
- {@const key = item.key}
1011
- {@const targetOnBehalfOf = getTargetOnBehalfOf(key)}
1012
- {@const isConflict = diff.ahead > 0 && diff.behind > 0}
1013
- {@const existsInBothWorkspaces = !(
1014
- (diff.exists_in_fork && !diff.exists_in_source) ||
1015
- (!diff.exists_in_fork && diff.exists_in_source)
1016
- )}
1017
- <!-- On-behalf-of selector -->
1018
- {#if itemNeedsOnBehalfOfSelection(key, diff.kind)}
1019
- <OnBehalfOfSelector
1020
- targetWorkspace={deployTargetWorkspace}
1021
- targetValue={targetOnBehalfOf}
1022
- selected={onBehalfOfChoice[key]}
1023
- onSelect={(choice, details) => {
1024
- onBehalfOfChoice[key] = choice
1025
- if (details) customOnBehalfOf[key] = details
1026
- }}
1027
- kind={diff.kind}
1028
- canPreserve={canPreserveOnBehalfOf}
1029
- customValue={customOnBehalfOf[key]?.permissionedAs}
1030
- />
1031
- {/if}
1032
- {#if diff.kind === 'raw_app'}
1033
- <Badge small icon={{ icon: FileJson }}>Raw</Badge>
1034
- {/if}
1035
- <!-- Status badges -->
1036
- {#if !diff.exists_in_fork && diff.exists_in_source && diff.ahead == 0 && diff.behind > 0}
1037
- <Badge
1038
- title="This item was newly created in the parent workspace '{parentWorkspaceId}'"
1039
- color="indigo"
1040
- size="xs">New</Badge
1041
- >
1042
- {/if}
1043
- {#if !diff.exists_in_fork && diff.exists_in_source && diff.ahead > 0}
1044
- <Badge title="This item was deleted in '{currentWorkspaceId}'" color="red" size="xs"
1045
- >Deleted</Badge
1046
- >
1047
- {/if}
1048
- {#if diff.exists_in_fork && !diff.exists_in_source && diff.behind > 0}
1049
- <Badge
1050
- title="This item was deleted in the parent workspace '{parentWorkspaceId}'"
1051
- color="red"
1052
- size="xs">Deleted</Badge
1053
- >
1054
- {/if}
1055
- {#if diff.exists_in_fork && !diff.exists_in_source && diff.ahead > 0 && diff.behind == 0}
1056
- <Badge
1057
- title="This item was newly created in '{currentWorkspaceId}'"
1058
- color="indigo"
1059
- size="xs">New</Badge
1060
- >
1061
- {/if}
1062
- {@const ciStatus = getCiTestStatus(diff)}
1063
- {#if ciStatus === 'pass'}
1064
- <Badge color="green" size="xs"><CircleCheck size={10} class="mr-0.5" />CI pass</Badge>
1065
- {:else if ciStatus === 'fail'}
1066
- <Badge color="red" size="xs"><CircleX size={10} class="mr-0.5" />CI fail</Badge>
1067
- {:else if ciStatus === 'running'}
1068
- <Badge color="yellow" size="xs"
1069
- ><Loader2 size={10} class="mr-0.5 animate-spin" />CI</Badge
776
+ </div>
777
+ <div class:invisible={!existsInBothWorkspaces}>
778
+ <Button
779
+ size="xs"
780
+ variant="subtle"
781
+ onclick={() => showDiff(diff.kind as Kind, diff.path)}
1070
782
  >
1071
- {/if}
1072
- {#if !deploymentStatus[key] || deploymentStatus[key].status != 'deployed'}
1073
- <div class="flex items-center gap-2">
1074
- {#if isConflict || existsInBothWorkspaces}
1075
- {#if diff.ahead > 0}
1076
- <Badge color="green" size="xs">
1077
- <ArrowUpRight class="w-3 h-3 inline" />
1078
- {diff.ahead} ahead
1079
- </Badge>
1080
- {/if}
1081
- {#if diff.behind > 0}
1082
- <Badge color="blue" size="xs">
1083
- <ArrowDownRight class="w-3 h-3 inline" />
1084
- {diff.behind} behind
1085
- </Badge>
1086
- {/if}
1087
- {#if isConflict}
1088
- <Badge color="orange" size="xs">
1089
- <AlertTriangle class="w-3 h-3 inline" />
1090
- Conflict
1091
- </Badge>
1092
- {/if}
1093
- {/if}
1094
- </div>
1095
- <div class:invisible={!existsInBothWorkspaces}>
1096
- <Button
1097
- size="xs"
1098
- variant="subtle"
1099
- onclick={() => showDiff(diff.kind as Kind, diff.path)}
1100
- >
1101
- <DiffIcon class="w-3 h-3" />
1102
- Show diff
1103
- </Button>
1104
- </div>
1105
- {/if}
783
+ <DiffIcon class="w-3 h-3" />
784
+ Show diff
785
+ </Button>
786
+ </div>
1106
787
  {/if}
1107
788
  {/snippet}
1108
789
 
@@ -1185,31 +866,6 @@ $effect(() => {
1185
866
  </div>
1186
867
 
1187
868
  <DiffDrawer bind:this={diffDrawer} {isFlow} />
1188
-
1189
- <Drawer bind:open={triggerDiffOpen} size="900px">
1190
- <DrawerContent
1191
- title={triggerDiffPayload
1192
- ? `${triggerDiffPayload.kindLabel} ${triggerDiffPayload.path}`
1193
- : 'Trigger diff'}
1194
- on:close={() => (triggerDiffOpen = false)}
1195
- >
1196
- {#if triggerDiffPayload}
1197
- <div class="flex flex-col h-full">
1198
- <div class="flex-1 min-h-0">
1199
- <DiffEditor
1200
- open={triggerDiffOpen}
1201
- className="!h-full"
1202
- defaultLang="yaml"
1203
- defaultOriginal={triggerDiffPayload.original}
1204
- defaultModified={triggerDiffPayload.modified}
1205
- readOnly
1206
- inlineDiff={false}
1207
- />
1208
- </div>
1209
- </div>
1210
- {/if}
1211
- </DrawerContent>
1212
- </Drawer>
1213
869
  {:else}
1214
870
  <div class="flex items-center justify-center h-full">
1215
871
  <div class="text-gray-500">No comparison data available</div>