@cat-factory/app 0.9.0 → 0.10.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/app/components/board/AddTaskModal.vue +209 -21
- package/app/components/board/nodes/BlockNode.vue +2 -2
- package/app/components/focus/BlockFocusView.vue +2 -2
- package/app/components/layout/CommandBar.vue +7 -33
- package/app/components/layout/IntegrationsHub.vue +230 -0
- package/app/components/layout/SideBar.vue +8 -170
- package/app/components/panels/GenericStructuredResultView.vue +131 -0
- package/app/components/panels/InspectorPanel.vue +6 -2
- package/app/components/panels/StepResultViewHost.vue +4 -0
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +148 -0
- package/app/components/settings/IssueTrackerWritebackPanel.vue +45 -57
- package/app/components/settings/MergeThresholdsPanel.vue +189 -226
- package/app/components/settings/ObservabilityConnectionPanel.vue +151 -0
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +46 -61
- package/app/components/settings/WorkspaceSettingsPanel.vue +136 -63
- package/app/composables/api/releaseHealth.ts +11 -10
- package/app/pages/index.vue +4 -8
- package/app/stores/agents.ts +27 -2
- package/app/stores/releaseHealth.ts +48 -12
- package/app/stores/ui.ts +34 -42
- package/app/stores/workspace.ts +4 -0
- package/app/types/domain.ts +33 -1
- package/app/types/execution.ts +6 -0
- package/app/types/releaseHealth.ts +19 -11
- package/app/utils/catalog.spec.ts +10 -0
- package/app/utils/catalog.ts +20 -6
- package/package.json +2 -2
- package/app/components/board/ContextPicker.vue +0 -367
- package/app/components/settings/DatadogPanel.vue +0 -213
|
@@ -68,17 +68,6 @@ watch(
|
|
|
68
68
|
>
|
|
69
69
|
Build a pipeline
|
|
70
70
|
</UButton>
|
|
71
|
-
<UButton
|
|
72
|
-
block
|
|
73
|
-
color="primary"
|
|
74
|
-
variant="soft"
|
|
75
|
-
size="sm"
|
|
76
|
-
icon="i-lucide-plus"
|
|
77
|
-
class="justify-start"
|
|
78
|
-
@click="ui.openCommandBar()"
|
|
79
|
-
>
|
|
80
|
-
Add a block
|
|
81
|
-
</UButton>
|
|
82
71
|
</div>
|
|
83
72
|
</section>
|
|
84
73
|
|
|
@@ -122,95 +111,20 @@ watch(
|
|
|
122
111
|
Integrations
|
|
123
112
|
</h2>
|
|
124
113
|
<div class="space-y-1.5">
|
|
114
|
+
<!-- Every external system the workspace can enable/link now lives behind
|
|
115
|
+
this single button — the hub modal lists them grouped (source control,
|
|
116
|
+
communication, documents, trackers, observability, model providers). -->
|
|
125
117
|
<UButton
|
|
126
|
-
v-if="github.available"
|
|
127
118
|
block
|
|
128
119
|
color="primary"
|
|
129
120
|
variant="soft"
|
|
130
121
|
size="sm"
|
|
131
|
-
icon="i-lucide-
|
|
122
|
+
icon="i-lucide-blocks"
|
|
132
123
|
class="justify-start"
|
|
133
|
-
@click="ui.
|
|
124
|
+
@click="ui.openIntegrations()"
|
|
134
125
|
>
|
|
135
|
-
|
|
136
|
-
{{ github.connected ? github.connection?.accountLogin : 'Connect GitHub' }}
|
|
137
|
-
</span>
|
|
126
|
+
Integrations
|
|
138
127
|
</UButton>
|
|
139
|
-
|
|
140
|
-
<UButton
|
|
141
|
-
v-if="slack.available"
|
|
142
|
-
block
|
|
143
|
-
color="primary"
|
|
144
|
-
variant="soft"
|
|
145
|
-
size="sm"
|
|
146
|
-
icon="i-lucide-slack"
|
|
147
|
-
class="justify-start"
|
|
148
|
-
@click="ui.openSlack()"
|
|
149
|
-
>
|
|
150
|
-
<span class="truncate">
|
|
151
|
-
{{ slack.connected ? slack.connection?.teamName : 'Connect Slack' }}
|
|
152
|
-
</span>
|
|
153
|
-
</UButton>
|
|
154
|
-
|
|
155
|
-
<template v-if="documents.available">
|
|
156
|
-
<UButton
|
|
157
|
-
v-for="src in documents.sources"
|
|
158
|
-
:key="src.source"
|
|
159
|
-
block
|
|
160
|
-
color="neutral"
|
|
161
|
-
variant="soft"
|
|
162
|
-
size="sm"
|
|
163
|
-
:icon="src.icon"
|
|
164
|
-
class="justify-start"
|
|
165
|
-
@click="ui.openDocumentConnect(src.source)"
|
|
166
|
-
>
|
|
167
|
-
<span class="truncate">
|
|
168
|
-
{{ documents.isConnected(src.source) ? src.label : `Connect ${src.label}` }}
|
|
169
|
-
</span>
|
|
170
|
-
</UButton>
|
|
171
|
-
<UButton
|
|
172
|
-
v-if="documents.anyConnected"
|
|
173
|
-
block
|
|
174
|
-
color="neutral"
|
|
175
|
-
variant="soft"
|
|
176
|
-
size="sm"
|
|
177
|
-
icon="i-lucide-file-down"
|
|
178
|
-
class="justify-start"
|
|
179
|
-
@click="ui.openDocumentImport(null)"
|
|
180
|
-
>
|
|
181
|
-
Import & spawn
|
|
182
|
-
</UButton>
|
|
183
|
-
</template>
|
|
184
|
-
|
|
185
|
-
<template v-if="tasks.available">
|
|
186
|
-
<UButton
|
|
187
|
-
v-for="src in tasks.sources"
|
|
188
|
-
:key="src.source"
|
|
189
|
-
block
|
|
190
|
-
color="neutral"
|
|
191
|
-
variant="soft"
|
|
192
|
-
size="sm"
|
|
193
|
-
:icon="src.icon"
|
|
194
|
-
class="justify-start"
|
|
195
|
-
@click="ui.openTaskConnect(src.source)"
|
|
196
|
-
>
|
|
197
|
-
<span class="truncate">
|
|
198
|
-
{{ tasks.isConnected(src.source) ? src.label : `Connect ${src.label}` }}
|
|
199
|
-
</span>
|
|
200
|
-
</UButton>
|
|
201
|
-
<UButton
|
|
202
|
-
v-if="tasks.anyConnected"
|
|
203
|
-
block
|
|
204
|
-
color="neutral"
|
|
205
|
-
variant="soft"
|
|
206
|
-
size="sm"
|
|
207
|
-
icon="i-lucide-file-down"
|
|
208
|
-
class="justify-start"
|
|
209
|
-
@click="ui.openTaskImport(null)"
|
|
210
|
-
>
|
|
211
|
-
Import issues
|
|
212
|
-
</UButton>
|
|
213
|
-
</template>
|
|
214
128
|
</div>
|
|
215
129
|
</section>
|
|
216
130
|
|
|
@@ -240,17 +154,8 @@ watch(
|
|
|
240
154
|
Configuration
|
|
241
155
|
</h2>
|
|
242
156
|
<div class="space-y-1.5">
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
color="primary"
|
|
246
|
-
variant="soft"
|
|
247
|
-
size="sm"
|
|
248
|
-
icon="i-lucide-git-merge"
|
|
249
|
-
class="justify-start"
|
|
250
|
-
@click="ui.openMergeThresholds()"
|
|
251
|
-
>
|
|
252
|
-
Merge thresholds
|
|
253
|
-
</UButton>
|
|
157
|
+
<!-- Merge thresholds, issue writeback and default service best practices are
|
|
158
|
+
now tabs inside Workspace settings. -->
|
|
254
159
|
<UButton
|
|
255
160
|
block
|
|
256
161
|
color="primary"
|
|
@@ -262,28 +167,6 @@ watch(
|
|
|
262
167
|
>
|
|
263
168
|
Workspace settings
|
|
264
169
|
</UButton>
|
|
265
|
-
<UButton
|
|
266
|
-
block
|
|
267
|
-
color="primary"
|
|
268
|
-
variant="soft"
|
|
269
|
-
size="sm"
|
|
270
|
-
icon="i-lucide-message-square-reply"
|
|
271
|
-
class="justify-start"
|
|
272
|
-
@click="ui.openIssueWriteback()"
|
|
273
|
-
>
|
|
274
|
-
Issue tracker writeback
|
|
275
|
-
</UButton>
|
|
276
|
-
<UButton
|
|
277
|
-
block
|
|
278
|
-
color="primary"
|
|
279
|
-
variant="soft"
|
|
280
|
-
size="sm"
|
|
281
|
-
icon="i-lucide-activity"
|
|
282
|
-
class="justify-start"
|
|
283
|
-
@click="ui.openDatadog()"
|
|
284
|
-
>
|
|
285
|
-
Post-release health
|
|
286
|
-
</UButton>
|
|
287
170
|
<UButton
|
|
288
171
|
block
|
|
289
172
|
color="primary"
|
|
@@ -295,51 +178,6 @@ watch(
|
|
|
295
178
|
>
|
|
296
179
|
Model Configuration
|
|
297
180
|
</UButton>
|
|
298
|
-
<UButton
|
|
299
|
-
block
|
|
300
|
-
color="primary"
|
|
301
|
-
variant="soft"
|
|
302
|
-
size="sm"
|
|
303
|
-
icon="i-lucide-book-open-check"
|
|
304
|
-
class="justify-start"
|
|
305
|
-
@click="ui.openServiceFragmentDefaults()"
|
|
306
|
-
>
|
|
307
|
-
Default service best practices
|
|
308
|
-
</UButton>
|
|
309
|
-
<UButton
|
|
310
|
-
block
|
|
311
|
-
color="primary"
|
|
312
|
-
variant="soft"
|
|
313
|
-
size="sm"
|
|
314
|
-
icon="i-lucide-key-round"
|
|
315
|
-
class="justify-start"
|
|
316
|
-
title="Connect LLM vendor subscriptions + provider API keys"
|
|
317
|
-
@click="ui.openVendorCredentials()"
|
|
318
|
-
>
|
|
319
|
-
Vendors & keys
|
|
320
|
-
</UButton>
|
|
321
|
-
<UButton
|
|
322
|
-
block
|
|
323
|
-
color="primary"
|
|
324
|
-
variant="soft"
|
|
325
|
-
size="sm"
|
|
326
|
-
icon="i-lucide-server"
|
|
327
|
-
class="justify-start"
|
|
328
|
-
@click="ui.openLocalModels()"
|
|
329
|
-
>
|
|
330
|
-
My local runners
|
|
331
|
-
</UButton>
|
|
332
|
-
<UButton
|
|
333
|
-
block
|
|
334
|
-
color="primary"
|
|
335
|
-
variant="soft"
|
|
336
|
-
size="sm"
|
|
337
|
-
icon="i-lucide-waypoints"
|
|
338
|
-
class="justify-start"
|
|
339
|
-
@click="ui.openOpenRouter()"
|
|
340
|
-
>
|
|
341
|
-
OpenRouter models
|
|
342
|
-
</UButton>
|
|
343
181
|
</div>
|
|
344
182
|
</section>
|
|
345
183
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Generic structured-result window — the default dedicated surface for a registered
|
|
3
|
+
// CUSTOM agent kind whose archetype declares `resultView: 'generic-structured'`. A custom
|
|
4
|
+
// `container-explore` agent returns structured JSON (the engine's `custom` channel), which
|
|
5
|
+
// is recorded on the step; this renders it read-only (pretty-printed JSON), alongside the
|
|
6
|
+
// agent's prose summary and the shared run metadata — so a proprietary kind ships a usable
|
|
7
|
+
// result view with ZERO bespoke frontend code. Opened via the universal result-view host,
|
|
8
|
+
// the same seam the requirements / tester windows use.
|
|
9
|
+
import { computed } from 'vue'
|
|
10
|
+
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
11
|
+
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
12
|
+
|
|
13
|
+
const board = useBoardStore()
|
|
14
|
+
const execution = useExecutionStore()
|
|
15
|
+
const agents = useAgentsStore()
|
|
16
|
+
|
|
17
|
+
// Shared seam contract (open/blockId/close + Escape). No `onOpen` loader: this window reads
|
|
18
|
+
// its data straight off the execution step, so there's nothing to fetch on open.
|
|
19
|
+
const { open, blockId, instanceId, stepIndex, close } = useResultView('generic-structured')
|
|
20
|
+
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
21
|
+
|
|
22
|
+
const instance = computed(() =>
|
|
23
|
+
instanceId.value === null ? null : (execution.getInstance(instanceId.value) ?? null),
|
|
24
|
+
)
|
|
25
|
+
const step = computed(() => {
|
|
26
|
+
if (instance.value === null || stepIndex.value === null) return null
|
|
27
|
+
return instance.value.steps[stepIndex.value] ?? null
|
|
28
|
+
})
|
|
29
|
+
const meta = computed(() => (step.value ? agents.get(step.value.agentKind) : undefined))
|
|
30
|
+
|
|
31
|
+
/** The agent's structured JSON, pretty-printed; null when the step produced none. */
|
|
32
|
+
const customJson = computed<string | null>(() => {
|
|
33
|
+
const custom = step.value?.custom
|
|
34
|
+
if (custom === undefined || custom === null) return null
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(custom, null, 2)
|
|
37
|
+
} catch {
|
|
38
|
+
return String(custom)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<Teleport to="body">
|
|
45
|
+
<div
|
|
46
|
+
v-if="open"
|
|
47
|
+
class="fixed inset-0 z-50 flex items-stretch justify-center bg-slate-950/70 backdrop-blur-sm"
|
|
48
|
+
@click.self="close"
|
|
49
|
+
>
|
|
50
|
+
<div
|
|
51
|
+
class="m-4 flex w-full max-w-4xl flex-col overflow-hidden rounded-2xl border border-slate-800 bg-slate-900 shadow-2xl"
|
|
52
|
+
>
|
|
53
|
+
<!-- Header -->
|
|
54
|
+
<header class="flex items-center gap-3 border-b border-slate-800 px-5 py-3">
|
|
55
|
+
<span
|
|
56
|
+
class="flex h-8 w-8 items-center justify-center rounded-lg bg-cyan-500/15 text-cyan-300"
|
|
57
|
+
>
|
|
58
|
+
<UIcon :name="meta?.icon ?? 'i-lucide-braces'" class="h-4 w-4" />
|
|
59
|
+
</span>
|
|
60
|
+
<div class="min-w-0 flex-1">
|
|
61
|
+
<h2 class="truncate text-sm font-semibold text-slate-100">
|
|
62
|
+
{{ meta?.label ?? 'Agent result' }}{{ block ? ` — ${block.title}` : '' }}
|
|
63
|
+
</h2>
|
|
64
|
+
<p class="truncate text-[11px] text-slate-400">
|
|
65
|
+
{{ meta?.description ?? 'Structured agent output' }}
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
<StepRestartControl
|
|
69
|
+
:instance-id="instanceId"
|
|
70
|
+
:step-index="stepIndex"
|
|
71
|
+
@restarted="close"
|
|
72
|
+
/>
|
|
73
|
+
<button
|
|
74
|
+
class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
|
|
75
|
+
@click="close"
|
|
76
|
+
>
|
|
77
|
+
<UIcon name="i-lucide-x" class="h-4 w-4" />
|
|
78
|
+
</button>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<div class="flex min-h-0 flex-1">
|
|
82
|
+
<!-- Main: prose summary + structured JSON -->
|
|
83
|
+
<div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
|
|
84
|
+
<p
|
|
85
|
+
v-if="step?.output"
|
|
86
|
+
class="mb-4 whitespace-pre-wrap text-[13px] leading-relaxed text-slate-300"
|
|
87
|
+
>
|
|
88
|
+
{{ step.output }}
|
|
89
|
+
</p>
|
|
90
|
+
|
|
91
|
+
<template v-if="customJson">
|
|
92
|
+
<h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
93
|
+
Structured output
|
|
94
|
+
</h3>
|
|
95
|
+
<pre
|
|
96
|
+
class="overflow-x-auto rounded-lg border border-slate-800 bg-slate-950/60 p-3 text-[12px] leading-relaxed text-slate-200"
|
|
97
|
+
><code>{{ customJson }}</code></pre>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<div
|
|
101
|
+
v-else-if="!step?.output"
|
|
102
|
+
class="flex h-full flex-col items-center justify-center gap-2 text-center text-slate-400"
|
|
103
|
+
>
|
|
104
|
+
<UIcon name="i-lucide-braces" class="h-8 w-8 opacity-40" />
|
|
105
|
+
<p class="text-sm">No result yet.</p>
|
|
106
|
+
<p class="max-w-sm text-[11px] text-slate-500">
|
|
107
|
+
The structured output appears once this agent finishes. While it runs, the step
|
|
108
|
+
shows live progress on the board.
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Sidebar: shared run metadata + observability rollup -->
|
|
114
|
+
<aside
|
|
115
|
+
class="hidden w-60 shrink-0 flex-col gap-4 border-l border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
|
|
116
|
+
>
|
|
117
|
+
<StepRunMeta
|
|
118
|
+
v-if="step"
|
|
119
|
+
:step="step"
|
|
120
|
+
:instance-id="instanceId ?? undefined"
|
|
121
|
+
:step-number="stepIndex === null ? undefined : stepIndex + 1"
|
|
122
|
+
:total-steps="instance?.steps.length"
|
|
123
|
+
:run-failed="instance?.status === 'failed'"
|
|
124
|
+
:failure-at="instance?.failure?.occurredAt"
|
|
125
|
+
/>
|
|
126
|
+
</aside>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</Teleport>
|
|
131
|
+
</template>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Block, BlockStatus } from '~/types/domain'
|
|
3
|
-
import {
|
|
3
|
+
import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
|
|
4
4
|
import TaskContextDocs from '~/components/documents/TaskContextDocs.vue'
|
|
5
5
|
import TaskContextIssues from '~/components/tasks/TaskContextIssues.vue'
|
|
6
6
|
import TaskAgentConfig from '~/components/panels/inspector/TaskAgentConfig.vue'
|
|
7
7
|
import ServiceTestConfig from '~/components/panels/inspector/ServiceTestConfig.vue'
|
|
8
8
|
import ServiceFragments from '~/components/panels/inspector/ServiceFragments.vue'
|
|
9
|
+
import ServiceReleaseHealthConfig from '~/components/panels/inspector/ServiceReleaseHealthConfig.vue'
|
|
9
10
|
import ContainerSummary from '~/components/panels/inspector/ContainerSummary.vue'
|
|
10
11
|
import TaskDependencies from '~/components/panels/inspector/TaskDependencies.vue'
|
|
11
12
|
import TaskStructure from '~/components/panels/inspector/TaskStructure.vue'
|
|
@@ -52,7 +53,7 @@ const isContainer = computed(() => level.value === 'frame' || level.value === 'm
|
|
|
52
53
|
const isTask = computed(() => level.value === 'task')
|
|
53
54
|
|
|
54
55
|
const instance = computed(() => execution.getInstance(block.value?.executionId))
|
|
55
|
-
const typeMeta = computed(() => (block.value ?
|
|
56
|
+
const typeMeta = computed(() => (block.value ? blockTypeMeta(block.value.type) : null))
|
|
56
57
|
|
|
57
58
|
// Containers show a derived activity status (never "done"); tasks use their own.
|
|
58
59
|
const FRAME_LABEL: Record<BlockStatus, string> = {
|
|
@@ -413,6 +414,9 @@ const showOriginalDescription = ref(false)
|
|
|
413
414
|
<!-- service (frame): best-practice fragments for code-aware agents -->
|
|
414
415
|
<ServiceFragments v-if="isFrame" :block="block" />
|
|
415
416
|
|
|
417
|
+
<!-- service (frame): post-release-health monitor/SLO mapping -->
|
|
418
|
+
<ServiceReleaseHealthConfig v-if="isFrame" :block="block" />
|
|
419
|
+
|
|
416
420
|
<!-- task: dependencies, structure, agent config, run settings, execution -->
|
|
417
421
|
<template v-else-if="isTask">
|
|
418
422
|
<RecurringScheduleSettings :block="block" />
|
|
@@ -16,6 +16,7 @@ import ClarityReviewWindow from '~/components/clarity/ClarityReviewWindow.vue'
|
|
|
16
16
|
import TestReportWindow from '~/components/testing/TestReportWindow.vue'
|
|
17
17
|
import GateResultView from '~/components/gates/GateResultView.vue'
|
|
18
18
|
import ConsensusSessionWindow from '~/components/consensus/ConsensusSessionWindow.vue'
|
|
19
|
+
import GenericStructuredResultView from '~/components/panels/GenericStructuredResultView.vue'
|
|
19
20
|
|
|
20
21
|
const ui = useUiStore()
|
|
21
22
|
|
|
@@ -27,6 +28,9 @@ const STEP_RESULT_VIEWS: Record<string, Component> = {
|
|
|
27
28
|
gate: GateResultView,
|
|
28
29
|
// Opened for any step that ran the consensus mechanism (routed in `ui.dispatchStepView`).
|
|
29
30
|
'consensus-session': ConsensusSessionWindow,
|
|
31
|
+
// Default dedicated view for a registered CUSTOM kind's structured (`custom`) output —
|
|
32
|
+
// a read-only JSON viewer, so a proprietary agent ships a result view with no bespoke code.
|
|
33
|
+
'generic-structured': GenericStructuredResultView,
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const active = computed<Component | null>(() => {
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, reactive, ref, watch } from 'vue'
|
|
3
|
+
import type { Block } from '~/types/domain'
|
|
4
|
+
|
|
5
|
+
// Per-service (frame) post-release-health mapping: which observability monitors/SLOs the
|
|
6
|
+
// `post-release-health` gate watches after this service's PRs ship. Keyed by THIS block's
|
|
7
|
+
// id (no manual entry) — the global window only owns the connection now. The Attach/save
|
|
8
|
+
// control is disabled with a hint until an observability integration is connected.
|
|
9
|
+
const props = defineProps<{ block: Block }>()
|
|
10
|
+
|
|
11
|
+
const store = useReleaseHealthStore()
|
|
12
|
+
const ui = useUiStore()
|
|
13
|
+
const toast = useToast()
|
|
14
|
+
|
|
15
|
+
const busy = ref(false)
|
|
16
|
+
const draft = reactive({ monitorIds: '', sloIds: '', envTag: '' })
|
|
17
|
+
|
|
18
|
+
const connected = computed(() => store.connection.connected)
|
|
19
|
+
const saved = computed(() => store.configForBlock(props.block.id))
|
|
20
|
+
|
|
21
|
+
function parseIds(csv: string): string[] {
|
|
22
|
+
return csv
|
|
23
|
+
.split(',')
|
|
24
|
+
.map((s) => s.trim())
|
|
25
|
+
.filter((s) => s.length > 0)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Load the connection + configs once, then hydrate the form from this block's saved config.
|
|
29
|
+
onMounted(() => {
|
|
30
|
+
store.ensureLoaded().catch(() => {})
|
|
31
|
+
})
|
|
32
|
+
watch(
|
|
33
|
+
saved,
|
|
34
|
+
(config) => {
|
|
35
|
+
draft.monitorIds = config?.monitorIds.join(', ') ?? ''
|
|
36
|
+
draft.sloIds = config?.sloIds.join(', ') ?? ''
|
|
37
|
+
draft.envTag = config?.envTag ?? ''
|
|
38
|
+
},
|
|
39
|
+
{ immediate: true },
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
function notifyError(title: string, e: unknown) {
|
|
43
|
+
toast.add({
|
|
44
|
+
title,
|
|
45
|
+
description: e instanceof Error ? e.message : String(e),
|
|
46
|
+
icon: 'i-lucide-triangle-alert',
|
|
47
|
+
color: 'error',
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function save() {
|
|
52
|
+
busy.value = true
|
|
53
|
+
try {
|
|
54
|
+
await store.saveConfig(props.block.id, {
|
|
55
|
+
monitorIds: parseIds(draft.monitorIds),
|
|
56
|
+
sloIds: parseIds(draft.sloIds),
|
|
57
|
+
envTag: draft.envTag.trim() || null,
|
|
58
|
+
})
|
|
59
|
+
toast.add({ title: 'Monitoring saved', icon: 'i-lucide-check', color: 'success' })
|
|
60
|
+
} catch (e) {
|
|
61
|
+
notifyError('Could not save the mapping', e)
|
|
62
|
+
} finally {
|
|
63
|
+
busy.value = false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function clear() {
|
|
68
|
+
busy.value = true
|
|
69
|
+
try {
|
|
70
|
+
await store.removeConfig(props.block.id)
|
|
71
|
+
draft.monitorIds = ''
|
|
72
|
+
draft.sloIds = ''
|
|
73
|
+
draft.envTag = ''
|
|
74
|
+
} catch (e) {
|
|
75
|
+
notifyError('Could not clear the mapping', e)
|
|
76
|
+
} finally {
|
|
77
|
+
busy.value = false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<div class="space-y-2">
|
|
84
|
+
<div class="flex items-center justify-between">
|
|
85
|
+
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
86
|
+
Post-release health
|
|
87
|
+
</span>
|
|
88
|
+
<UButton
|
|
89
|
+
v-if="saved"
|
|
90
|
+
color="error"
|
|
91
|
+
variant="ghost"
|
|
92
|
+
size="xs"
|
|
93
|
+
icon="i-lucide-trash-2"
|
|
94
|
+
:loading="busy"
|
|
95
|
+
@click="clear"
|
|
96
|
+
>
|
|
97
|
+
Clear
|
|
98
|
+
</UButton>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- Disabled affordance until an observability integration is connected. -->
|
|
102
|
+
<div
|
|
103
|
+
v-if="!connected"
|
|
104
|
+
class="flex items-center justify-between gap-2 rounded-md border border-slate-800 bg-slate-900/60 px-2 py-1.5 text-[11px] text-slate-400"
|
|
105
|
+
>
|
|
106
|
+
<span>Connect an observability integration to watch this service after releases.</span>
|
|
107
|
+
<UButton
|
|
108
|
+
color="neutral"
|
|
109
|
+
variant="soft"
|
|
110
|
+
size="xs"
|
|
111
|
+
icon="i-lucide-plug"
|
|
112
|
+
title="Connect an observability integration first"
|
|
113
|
+
@click="ui.openObservabilityConnection()"
|
|
114
|
+
>
|
|
115
|
+
Connect
|
|
116
|
+
</UButton>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div v-else class="space-y-2">
|
|
120
|
+
<p class="text-[11px] text-slate-500">
|
|
121
|
+
The monitors and SLOs the gate watches after this service ships. Comma-separate ids.
|
|
122
|
+
</p>
|
|
123
|
+
<div class="grid grid-cols-2 gap-2">
|
|
124
|
+
<UFormField label="Monitor ids">
|
|
125
|
+
<UInput v-model="draft.monitorIds" placeholder="123, 456" size="sm" class="w-full" />
|
|
126
|
+
</UFormField>
|
|
127
|
+
<UFormField label="SLO ids">
|
|
128
|
+
<UInput v-model="draft.sloIds" placeholder="abc, def" size="sm" class="w-full" />
|
|
129
|
+
</UFormField>
|
|
130
|
+
</div>
|
|
131
|
+
<UFormField label="Env tag (optional)">
|
|
132
|
+
<UInput v-model="draft.envTag" placeholder="prod" size="sm" class="w-full" />
|
|
133
|
+
</UFormField>
|
|
134
|
+
<div class="flex justify-end">
|
|
135
|
+
<UButton
|
|
136
|
+
color="primary"
|
|
137
|
+
variant="soft"
|
|
138
|
+
size="xs"
|
|
139
|
+
icon="i-lucide-save"
|
|
140
|
+
:loading="busy"
|
|
141
|
+
@click="save"
|
|
142
|
+
>
|
|
143
|
+
Save monitoring
|
|
144
|
+
</UButton>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</template>
|
|
@@ -4,27 +4,23 @@
|
|
|
4
4
|
// progresses — comment when the PR opens, and comment + close as resolved when it
|
|
5
5
|
// merges. Each is overridable per task in the inspector. Persisted on the workspace
|
|
6
6
|
// tracker settings (the selection + Jira project key are preserved on save).
|
|
7
|
-
import { ref, watch } from 'vue'
|
|
7
|
+
import { onMounted, ref, watch } from 'vue'
|
|
8
8
|
|
|
9
|
-
const ui = useUiStore()
|
|
10
9
|
const tracker = useTrackerStore()
|
|
11
10
|
const toast = useToast()
|
|
12
11
|
|
|
13
|
-
const open = computed({
|
|
14
|
-
get: () => ui.issueWritebackOpen,
|
|
15
|
-
set: (v: boolean) => (v ? ui.openIssueWriteback() : ui.closeIssueWriteback()),
|
|
16
|
-
})
|
|
17
|
-
|
|
18
12
|
const commentOnPrOpen = ref(false)
|
|
19
13
|
const resolveOnMerge = ref(false)
|
|
20
14
|
const saving = ref(false)
|
|
21
15
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
// Sync the local toggles from the store on mount (the tab renders when Workspace
|
|
17
|
+
// settings opens) and whenever the stored settings change underneath.
|
|
18
|
+
function hydrate() {
|
|
25
19
|
commentOnPrOpen.value = tracker.settings.writebackCommentOnPrOpen
|
|
26
20
|
resolveOnMerge.value = tracker.settings.writebackResolveOnMerge
|
|
27
|
-
}
|
|
21
|
+
}
|
|
22
|
+
onMounted(hydrate)
|
|
23
|
+
watch(() => tracker.settings, hydrate, { deep: true })
|
|
28
24
|
|
|
29
25
|
async function save() {
|
|
30
26
|
saving.value = true
|
|
@@ -51,53 +47,45 @@ async function save() {
|
|
|
51
47
|
</script>
|
|
52
48
|
|
|
53
49
|
<template>
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
first status in their <span class="text-slate-300">Done</span> category.
|
|
62
|
-
</p>
|
|
50
|
+
<div class="space-y-4">
|
|
51
|
+
<p class="text-xs text-slate-400">
|
|
52
|
+
When a task is linked to a tracker issue (GitHub Issues or Jira), write back to it as the
|
|
53
|
+
task's pull request progresses. Each toggle is the workspace default and can be overridden per
|
|
54
|
+
task in the inspector. GitHub issues close natively; Jira issues transition to the first
|
|
55
|
+
status in their <span class="text-slate-300">Done</span> category.
|
|
56
|
+
</p>
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
</span>
|
|
74
|
-
</label>
|
|
58
|
+
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
59
|
+
<USwitch v-model="commentOnPrOpen" />
|
|
60
|
+
<span class="text-sm">
|
|
61
|
+
<span class="block text-slate-200">Comment when a PR opens</span>
|
|
62
|
+
<span class="block text-xs text-slate-500">
|
|
63
|
+
Post a comment on the linked issue with the new pull request's link.
|
|
64
|
+
</span>
|
|
65
|
+
</span>
|
|
66
|
+
</label>
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</span>
|
|
86
|
-
</label>
|
|
68
|
+
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
69
|
+
<USwitch v-model="resolveOnMerge" />
|
|
70
|
+
<span class="text-sm">
|
|
71
|
+
<span class="block text-slate-200">Close as resolved when a PR merges</span>
|
|
72
|
+
<span class="block text-xs text-slate-500">
|
|
73
|
+
Comment that the PR merged, then close / resolve the linked issue.
|
|
74
|
+
</span>
|
|
75
|
+
</span>
|
|
76
|
+
</label>
|
|
87
77
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</template>
|
|
102
|
-
</UModal>
|
|
78
|
+
<div class="flex justify-end">
|
|
79
|
+
<UButton
|
|
80
|
+
color="primary"
|
|
81
|
+
variant="soft"
|
|
82
|
+
size="sm"
|
|
83
|
+
icon="i-lucide-save"
|
|
84
|
+
:loading="saving"
|
|
85
|
+
@click="save"
|
|
86
|
+
>
|
|
87
|
+
Save
|
|
88
|
+
</UButton>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
103
91
|
</template>
|