@cat-factory/app 0.12.0 → 0.14.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/app.config.ts +18 -0
- package/app/components/layout/IntegrationsHub.vue +16 -6
- package/app/components/panels/InspectorPanel.vue +14 -0
- package/app/components/panels/StepResultViewHost.vue +4 -0
- package/app/components/settings/IssueTrackerPanel.vue +393 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +9 -9
- package/app/components/spec/ServiceSpecWindow.vue +348 -0
- package/app/composables/api/spec.ts +14 -0
- package/app/composables/api/tasks.ts +8 -0
- package/app/composables/useApi.ts +2 -0
- package/app/stores/serviceSpec.ts +72 -0
- package/app/stores/tasks.ts +44 -2
- package/app/stores/ui.ts +5 -0
- package/app/types/spec.ts +74 -0
- package/app/types/tasks.ts +25 -0
- package/nuxt.config.ts +9 -0
- package/package.json +1 -1
- package/app/components/settings/IssueTrackerWritebackPanel.vue +0 -91
package/app/app.config.ts
CHANGED
|
@@ -4,5 +4,23 @@ export default defineAppConfig({
|
|
|
4
4
|
primary: 'indigo',
|
|
5
5
|
neutral: 'slate',
|
|
6
6
|
},
|
|
7
|
+
// Give every overlay the same layered dark palette the agent-run-details
|
|
8
|
+
// reader uses: a deep slate-950 surface so the slate-900 panels/cards inside
|
|
9
|
+
// pop, with slate-800 chrome. Applies to all UModal/USlideover instances so
|
|
10
|
+
// overlays stay consistent without per-instance `:ui` overrides.
|
|
11
|
+
modal: {
|
|
12
|
+
slots: {
|
|
13
|
+
content: 'bg-slate-950 ring-slate-800 divide-slate-800',
|
|
14
|
+
header: 'border-b border-slate-800',
|
|
15
|
+
title: 'text-white',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
slideover: {
|
|
19
|
+
slots: {
|
|
20
|
+
content: 'bg-slate-950 ring-slate-800 divide-slate-800',
|
|
21
|
+
header: 'border-b border-slate-800',
|
|
22
|
+
title: 'text-white',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
7
25
|
},
|
|
8
26
|
})
|
|
@@ -12,8 +12,16 @@ const github = useGitHubStore()
|
|
|
12
12
|
const slack = useSlackStore()
|
|
13
13
|
const documents = useDocumentsStore()
|
|
14
14
|
const tasks = useTasksStore()
|
|
15
|
+
const tracker = useTrackerStore()
|
|
15
16
|
const releaseHealth = useReleaseHealthStore()
|
|
16
17
|
|
|
18
|
+
// The selected filing tracker, as a badge label ("GitHub Issues" / "Jira").
|
|
19
|
+
const trackerLabel = computed(() => {
|
|
20
|
+
if (tracker.settings.tracker === 'github') return 'GitHub Issues'
|
|
21
|
+
if (tracker.settings.tracker === 'jira') return 'Jira'
|
|
22
|
+
return undefined
|
|
23
|
+
})
|
|
24
|
+
|
|
17
25
|
// The observability connection status drives the hub's connected badge. Load it
|
|
18
26
|
// lazily when the hub opens (the secret-less connection view is cheap).
|
|
19
27
|
watch(
|
|
@@ -130,11 +138,13 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
130
138
|
})
|
|
131
139
|
}
|
|
132
140
|
trackers.push({
|
|
133
|
-
key: 'task:
|
|
134
|
-
icon: 'i-lucide-
|
|
135
|
-
label: 'Issue tracker
|
|
136
|
-
description: '
|
|
137
|
-
|
|
141
|
+
key: 'task:tracker',
|
|
142
|
+
icon: 'i-lucide-list-checks',
|
|
143
|
+
label: 'Issue tracker settings',
|
|
144
|
+
description: 'Choose the filing tracker, enable linking sources, and configure writeback.',
|
|
145
|
+
status: trackerLabel.value,
|
|
146
|
+
connected: tracker.settings.tracker !== null,
|
|
147
|
+
onClick: () => go(() => ui.openWorkspaceSettings('tracker')),
|
|
138
148
|
})
|
|
139
149
|
out.push({ title: 'Task trackers', items: trackers })
|
|
140
150
|
}
|
|
@@ -209,7 +219,7 @@ const groups = computed<IntegrationGroup[]>(() => {
|
|
|
209
219
|
v-for="item in group.items"
|
|
210
220
|
:key="item.key"
|
|
211
221
|
type="button"
|
|
212
|
-
class="flex w-full items-center gap-3 rounded-lg border border-slate-
|
|
222
|
+
class="flex w-full items-center gap-3 rounded-lg border border-slate-800 bg-slate-900/50 px-3 py-2.5 text-left transition hover:border-slate-700 hover:bg-slate-900"
|
|
213
223
|
@click="item.onClick()"
|
|
214
224
|
>
|
|
215
225
|
<UIcon :name="item.icon" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
@@ -406,6 +406,20 @@ const showOriginalDescription = ref(false)
|
|
|
406
406
|
<!-- task: context issues (tracker) -->
|
|
407
407
|
<TaskContextIssues v-if="isTask" :block="block" />
|
|
408
408
|
|
|
409
|
+
<!-- service (frame): navigate the prescriptive spec tree (+ Gherkin scenarios when
|
|
410
|
+
the spec is on the repo's default branch) -->
|
|
411
|
+
<UButton
|
|
412
|
+
v-if="isFrame"
|
|
413
|
+
block
|
|
414
|
+
color="neutral"
|
|
415
|
+
variant="soft"
|
|
416
|
+
size="sm"
|
|
417
|
+
icon="i-lucide-scroll-text"
|
|
418
|
+
@click="ui.openServiceSpec(block.id)"
|
|
419
|
+
>
|
|
420
|
+
View Requirements
|
|
421
|
+
</UButton>
|
|
422
|
+
|
|
409
423
|
<!-- service / module: tasks summary -->
|
|
410
424
|
<ContainerSummary v-if="isContainer" :block="block" />
|
|
411
425
|
<!-- service (frame): test infra + provisioning configuration -->
|
|
@@ -17,6 +17,7 @@ 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
19
|
import GenericStructuredResultView from '~/components/panels/GenericStructuredResultView.vue'
|
|
20
|
+
import ServiceSpecWindow from '~/components/spec/ServiceSpecWindow.vue'
|
|
20
21
|
|
|
21
22
|
const ui = useUiStore()
|
|
22
23
|
|
|
@@ -31,6 +32,9 @@ const STEP_RESULT_VIEWS: Record<string, Component> = {
|
|
|
31
32
|
// Default dedicated view for a registered CUSTOM kind's structured (`custom`) output —
|
|
32
33
|
// a read-only JSON viewer, so a proprietary agent ships a result view with no bespoke code.
|
|
33
34
|
'generic-structured': GenericStructuredResultView,
|
|
35
|
+
// The service's prescriptive spec tree (+ Gherkin), opened from the inspector's "View
|
|
36
|
+
// Requirements" button. Not a pipeline-step view — opened directly via `ui.openServiceSpec`.
|
|
37
|
+
'service-spec': ServiceSpecWindow,
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
const active = computed<Component | null>(() => {
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Workspace settings: a single, first-class home for issue tracking. It gathers
|
|
3
|
+
// the three things that used to be scattered (and, for the filing tracker, were
|
|
4
|
+
// only reachable buried inside the tech-debt recurring-pipeline modal):
|
|
5
|
+
//
|
|
6
|
+
// 1. Filing tracker — which tracker the tech-debt pipeline's `tracker` step
|
|
7
|
+
// files its ticket in (GitHub Issues / Jira / none).
|
|
8
|
+
// 2. Linking sources — the per-workspace on/off toggle for each source
|
|
9
|
+
// (task_source_settings) that governs whether issues can be imported and
|
|
10
|
+
// linked to tasks as agent context.
|
|
11
|
+
// 3. Writeback — comment on a task's linked issue when its PR opens, and
|
|
12
|
+
// comment + close it when the PR merges.
|
|
13
|
+
//
|
|
14
|
+
// Filing and linking are independent (filing rides the App / Jira connection
|
|
15
|
+
// directly; linking is the source toggle), so both are shown explicitly to undo
|
|
16
|
+
// the common confusion that "I have the GitHub App, why is nothing surfaced?".
|
|
17
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
18
|
+
import type { TaskSourceDiagnosticStatus, TaskSourceKind, TrackerKind } from '~/types/domain'
|
|
19
|
+
|
|
20
|
+
const tracker = useTrackerStore()
|
|
21
|
+
const tasks = useTasksStore()
|
|
22
|
+
const ui = useUiStore()
|
|
23
|
+
const toast = useToast()
|
|
24
|
+
|
|
25
|
+
// --- filing tracker + writeback (one Save, persisted on tracker settings) -----
|
|
26
|
+
const trackerKind = ref<TrackerKind | null>(null)
|
|
27
|
+
const jiraProjectKey = ref('')
|
|
28
|
+
const commentOnPrOpen = ref(false)
|
|
29
|
+
const resolveOnMerge = ref(false)
|
|
30
|
+
const saving = ref(false)
|
|
31
|
+
|
|
32
|
+
function hydrate() {
|
|
33
|
+
trackerKind.value = tracker.settings.tracker
|
|
34
|
+
jiraProjectKey.value = tracker.settings.jiraProjectKey ?? ''
|
|
35
|
+
commentOnPrOpen.value = tracker.settings.writebackCommentOnPrOpen
|
|
36
|
+
resolveOnMerge.value = tracker.settings.writebackResolveOnMerge
|
|
37
|
+
}
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
hydrate()
|
|
40
|
+
// The descriptors (availability + enable state) come from the task-source probe;
|
|
41
|
+
// probe on open if the navbar hasn't already, so the toggles below reflect reality.
|
|
42
|
+
if (tasks.available === null) void tasks.probe()
|
|
43
|
+
})
|
|
44
|
+
watch(() => tracker.settings, hydrate, { deep: true })
|
|
45
|
+
|
|
46
|
+
// Per-source live state (available = usable now; enabled = offered to the workspace).
|
|
47
|
+
const github = computed(() => tasks.descriptorFor('github'))
|
|
48
|
+
const jira = computed(() => tasks.descriptorFor('jira'))
|
|
49
|
+
|
|
50
|
+
// A tracker can only file where it can authenticate: GitHub rides the installed
|
|
51
|
+
// App, Jira needs a connection. Selecting an unusable tracker is allowed (it just
|
|
52
|
+
// won't file until set up), but we surface the gap inline.
|
|
53
|
+
const githubAvailable = computed(() => github.value?.available ?? false)
|
|
54
|
+
const jiraConnected = computed(() => tasks.isConnected('jira'))
|
|
55
|
+
|
|
56
|
+
// Jira needs a project key to file into; block Save on an empty one when picked.
|
|
57
|
+
const canSave = computed(
|
|
58
|
+
() => trackerKind.value !== 'jira' || jiraProjectKey.value.trim().length > 0,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async function save() {
|
|
62
|
+
if (!canSave.value) return
|
|
63
|
+
saving.value = true
|
|
64
|
+
try {
|
|
65
|
+
await tracker.save({
|
|
66
|
+
tracker: trackerKind.value,
|
|
67
|
+
jiraProjectKey: trackerKind.value === 'jira' ? jiraProjectKey.value.trim() : null,
|
|
68
|
+
writebackCommentOnPrOpen: commentOnPrOpen.value,
|
|
69
|
+
writebackResolveOnMerge: resolveOnMerge.value,
|
|
70
|
+
})
|
|
71
|
+
toast.add({ title: 'Issue tracker saved', icon: 'i-lucide-check', color: 'success' })
|
|
72
|
+
} catch (e) {
|
|
73
|
+
toast.add({
|
|
74
|
+
title: 'Could not save settings',
|
|
75
|
+
description: e instanceof Error ? e.message : String(e),
|
|
76
|
+
icon: 'i-lucide-triangle-alert',
|
|
77
|
+
color: 'error',
|
|
78
|
+
})
|
|
79
|
+
} finally {
|
|
80
|
+
saving.value = false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- linking sources (per-source toggle, saved immediately) -------------------
|
|
85
|
+
const togglingSource = ref<TaskSourceKind | null>(null)
|
|
86
|
+
|
|
87
|
+
async function toggleSource(source: TaskSourceKind, enabled: boolean) {
|
|
88
|
+
togglingSource.value = source
|
|
89
|
+
try {
|
|
90
|
+
await tasks.setEnabled(source, enabled)
|
|
91
|
+
} catch (e) {
|
|
92
|
+
toast.add({
|
|
93
|
+
title: 'Could not update',
|
|
94
|
+
description: e instanceof Error ? e.message : String(e),
|
|
95
|
+
icon: 'i-lucide-triangle-alert',
|
|
96
|
+
color: 'error',
|
|
97
|
+
})
|
|
98
|
+
} finally {
|
|
99
|
+
togglingSource.value = null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- live "check setup" -------------------------------------------------------
|
|
104
|
+
// The probe failed entirely (not a per-source state): the whole integration is
|
|
105
|
+
// off or the backend errored, so the panel can't show real source state. We
|
|
106
|
+
// translate the captured status into a plain explanation + next step.
|
|
107
|
+
const probeFailureHint = computed(() => {
|
|
108
|
+
const err = tasks.probeError
|
|
109
|
+
if (tasks.available !== false || !err) return null
|
|
110
|
+
if (err.status === 503) {
|
|
111
|
+
return 'The task-source integration is turned off on this deployment (its encryption key is not configured). Set ENCRYPTION_KEY on the backend to enable issue tracking.'
|
|
112
|
+
}
|
|
113
|
+
if (err.status && err.status >= 500) {
|
|
114
|
+
return `The issue-tracker service returned an error (HTTP ${err.status}): ${err.message}. This usually means the backend isn't fully migrated/configured.`
|
|
115
|
+
}
|
|
116
|
+
return `Couldn't load issue-tracker settings${err.status ? ` (HTTP ${err.status})` : ''}: ${err.message}`
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
async function checkSetup(source: TaskSourceKind) {
|
|
120
|
+
try {
|
|
121
|
+
await tasks.checkSetup(source)
|
|
122
|
+
} catch (e) {
|
|
123
|
+
toast.add({
|
|
124
|
+
title: 'Check failed',
|
|
125
|
+
description: e instanceof Error ? e.message : String(e),
|
|
126
|
+
icon: 'i-lucide-triangle-alert',
|
|
127
|
+
color: 'error',
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Status → presentation for a setup-check verdict.
|
|
133
|
+
const STATUS_UI: Record<
|
|
134
|
+
TaskSourceDiagnosticStatus,
|
|
135
|
+
{ color: 'success' | 'warning' | 'error' | 'neutral'; icon: string }
|
|
136
|
+
> = {
|
|
137
|
+
ready: { color: 'success', icon: 'i-lucide-circle-check' },
|
|
138
|
+
not_installed: { color: 'warning', icon: 'i-lucide-download' },
|
|
139
|
+
not_connected: { color: 'warning', icon: 'i-lucide-plug' },
|
|
140
|
+
auth_failed: { color: 'error', icon: 'i-lucide-key-round' },
|
|
141
|
+
forbidden: { color: 'error', icon: 'i-lucide-shield-x' },
|
|
142
|
+
unreachable: { color: 'error', icon: 'i-lucide-wifi-off' },
|
|
143
|
+
error: { color: 'error', icon: 'i-lucide-triangle-alert' },
|
|
144
|
+
}
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<template>
|
|
148
|
+
<div class="space-y-7">
|
|
149
|
+
<!-- Whole-integration failure: explain WHY nothing is surfaced, instead of the
|
|
150
|
+
passive per-source "install first" hints (which would be misleading here). -->
|
|
151
|
+
<UAlert
|
|
152
|
+
v-if="probeFailureHint"
|
|
153
|
+
color="error"
|
|
154
|
+
variant="subtle"
|
|
155
|
+
icon="i-lucide-triangle-alert"
|
|
156
|
+
title="Issue tracking isn't available"
|
|
157
|
+
:description="probeFailureHint"
|
|
158
|
+
/>
|
|
159
|
+
|
|
160
|
+
<!-- 1. Filing tracker ----------------------------------------------------->
|
|
161
|
+
<section class="space-y-3">
|
|
162
|
+
<div>
|
|
163
|
+
<h3 class="text-sm font-semibold text-slate-200">Where tickets are filed</h3>
|
|
164
|
+
<p class="mt-1 text-[11px] text-slate-400">
|
|
165
|
+
The tech-debt recurring pipeline raises an issue before implementation and files it in
|
|
166
|
+
this tracker. Choose <span class="text-slate-300">None</span> to skip filing.
|
|
167
|
+
</p>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div class="flex flex-wrap gap-2">
|
|
171
|
+
<UButton
|
|
172
|
+
icon="i-lucide-circle-slash"
|
|
173
|
+
size="sm"
|
|
174
|
+
:color="trackerKind === null ? 'primary' : 'neutral'"
|
|
175
|
+
:variant="trackerKind === null ? 'solid' : 'subtle'"
|
|
176
|
+
@click="trackerKind = null"
|
|
177
|
+
>
|
|
178
|
+
None
|
|
179
|
+
</UButton>
|
|
180
|
+
<UButton
|
|
181
|
+
icon="i-lucide-github"
|
|
182
|
+
size="sm"
|
|
183
|
+
:color="trackerKind === 'github' ? 'primary' : 'neutral'"
|
|
184
|
+
:variant="trackerKind === 'github' ? 'solid' : 'subtle'"
|
|
185
|
+
@click="trackerKind = 'github'"
|
|
186
|
+
>
|
|
187
|
+
GitHub Issues
|
|
188
|
+
</UButton>
|
|
189
|
+
<UButton
|
|
190
|
+
icon="i-lucide-trello"
|
|
191
|
+
size="sm"
|
|
192
|
+
:color="trackerKind === 'jira' ? 'primary' : 'neutral'"
|
|
193
|
+
:variant="trackerKind === 'jira' ? 'solid' : 'subtle'"
|
|
194
|
+
@click="trackerKind = 'jira'"
|
|
195
|
+
>
|
|
196
|
+
Jira
|
|
197
|
+
</UButton>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Inline readiness hints for the picked tracker. -->
|
|
201
|
+
<p v-if="trackerKind === 'github' && !githubAvailable" class="text-[11px] text-amber-400">
|
|
202
|
+
GitHub Issues rides your installed GitHub App, which isn't connected yet. Install it under
|
|
203
|
+
<button class="underline" @click="ui.openGitHub()">Integrations → GitHub</button> — filing
|
|
204
|
+
stays off until then.
|
|
205
|
+
</p>
|
|
206
|
+
<p v-else-if="trackerKind === 'jira' && !jiraConnected" class="text-[11px] text-amber-400">
|
|
207
|
+
Jira isn't connected yet.
|
|
208
|
+
<button class="underline" @click="ui.openTaskConnect('jira')">Connect it</button> to file
|
|
209
|
+
and link issues.
|
|
210
|
+
</p>
|
|
211
|
+
|
|
212
|
+
<UFormField v-if="trackerKind === 'jira'" label="Jira project key" class="w-48">
|
|
213
|
+
<UInput v-model="jiraProjectKey" placeholder="ENG" size="sm" class="w-full" />
|
|
214
|
+
<template #help>
|
|
215
|
+
<span class="text-[11px] text-slate-500">New tickets are filed under this project.</span>
|
|
216
|
+
</template>
|
|
217
|
+
</UFormField>
|
|
218
|
+
</section>
|
|
219
|
+
|
|
220
|
+
<!-- 2. Linking sources ---------------------------------------------------->
|
|
221
|
+
<section class="space-y-3">
|
|
222
|
+
<div>
|
|
223
|
+
<h3 class="text-sm font-semibold text-slate-200">Link issues as context</h3>
|
|
224
|
+
<p class="mt-1 text-[11px] text-slate-400">
|
|
225
|
+
When a source is offered you can import its issues and attach them to a task, so agents
|
|
226
|
+
see the issue description and comments while implementing. This is independent of the
|
|
227
|
+
filing tracker above.
|
|
228
|
+
</p>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<!-- GitHub Issues source -->
|
|
232
|
+
<div class="rounded-lg border border-slate-800 bg-slate-800/40 px-3 py-2.5">
|
|
233
|
+
<div class="flex items-center justify-between gap-2">
|
|
234
|
+
<div class="flex min-w-0 items-center gap-2.5">
|
|
235
|
+
<UIcon name="i-lucide-github" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
236
|
+
<div class="min-w-0">
|
|
237
|
+
<div class="text-sm font-medium text-slate-200">GitHub Issues</div>
|
|
238
|
+
<div class="text-[11px] text-slate-500">
|
|
239
|
+
{{
|
|
240
|
+
githubAvailable
|
|
241
|
+
? 'Rides your installed GitHub App — no credentials needed.'
|
|
242
|
+
: 'Install the GitHub App (Integrations → GitHub) to offer this source.'
|
|
243
|
+
}}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
248
|
+
<UButton
|
|
249
|
+
size="xs"
|
|
250
|
+
color="neutral"
|
|
251
|
+
variant="ghost"
|
|
252
|
+
icon="i-lucide-stethoscope"
|
|
253
|
+
:loading="tasks.checking === 'github'"
|
|
254
|
+
@click="checkSetup('github')"
|
|
255
|
+
>
|
|
256
|
+
Check setup
|
|
257
|
+
</UButton>
|
|
258
|
+
<USwitch
|
|
259
|
+
v-if="githubAvailable"
|
|
260
|
+
:model-value="github?.enabled ?? false"
|
|
261
|
+
:loading="togglingSource === 'github'"
|
|
262
|
+
@update:model-value="(v: boolean) => toggleSource('github', v)"
|
|
263
|
+
/>
|
|
264
|
+
<UButton
|
|
265
|
+
v-else
|
|
266
|
+
size="xs"
|
|
267
|
+
color="neutral"
|
|
268
|
+
variant="soft"
|
|
269
|
+
icon="i-lucide-github"
|
|
270
|
+
@click="ui.openGitHub()"
|
|
271
|
+
>
|
|
272
|
+
Install
|
|
273
|
+
</UButton>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
<UAlert
|
|
277
|
+
v-if="tasks.diagnostics.github"
|
|
278
|
+
class="mt-2.5"
|
|
279
|
+
:color="STATUS_UI[tasks.diagnostics.github.status].color"
|
|
280
|
+
variant="subtle"
|
|
281
|
+
:icon="STATUS_UI[tasks.diagnostics.github.status].icon"
|
|
282
|
+
:description="
|
|
283
|
+
tasks.diagnostics.github.message +
|
|
284
|
+
(tasks.diagnostics.github.detail ? ` ${tasks.diagnostics.github.detail}` : '')
|
|
285
|
+
"
|
|
286
|
+
:ui="{ description: 'text-[11px]' }"
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<!-- Jira source -->
|
|
291
|
+
<div class="rounded-lg border border-slate-800 bg-slate-800/40 px-3 py-2.5">
|
|
292
|
+
<div class="flex items-center justify-between gap-2">
|
|
293
|
+
<div class="flex min-w-0 items-center gap-2.5">
|
|
294
|
+
<UIcon name="i-lucide-trello" class="h-5 w-5 shrink-0 text-slate-300" />
|
|
295
|
+
<div class="min-w-0">
|
|
296
|
+
<div class="text-sm font-medium text-slate-200">Jira</div>
|
|
297
|
+
<div class="text-[11px] text-slate-500">
|
|
298
|
+
{{ jiraConnected ? 'Connected.' : 'Connect with a Jira account and API token.' }}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
303
|
+
<UButton
|
|
304
|
+
v-if="jiraConnected"
|
|
305
|
+
size="xs"
|
|
306
|
+
color="neutral"
|
|
307
|
+
variant="ghost"
|
|
308
|
+
icon="i-lucide-stethoscope"
|
|
309
|
+
:loading="tasks.checking === 'jira'"
|
|
310
|
+
@click="checkSetup('jira')"
|
|
311
|
+
>
|
|
312
|
+
Check setup
|
|
313
|
+
</UButton>
|
|
314
|
+
<USwitch
|
|
315
|
+
v-if="jira?.available"
|
|
316
|
+
:model-value="jira?.enabled ?? false"
|
|
317
|
+
:loading="togglingSource === 'jira'"
|
|
318
|
+
@update:model-value="(v: boolean) => toggleSource('jira', v)"
|
|
319
|
+
/>
|
|
320
|
+
<UButton
|
|
321
|
+
v-else
|
|
322
|
+
size="xs"
|
|
323
|
+
color="neutral"
|
|
324
|
+
variant="soft"
|
|
325
|
+
icon="i-lucide-plug"
|
|
326
|
+
@click="ui.openTaskConnect('jira')"
|
|
327
|
+
>
|
|
328
|
+
Connect
|
|
329
|
+
</UButton>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
<UAlert
|
|
333
|
+
v-if="tasks.diagnostics.jira"
|
|
334
|
+
class="mt-2.5"
|
|
335
|
+
:color="STATUS_UI[tasks.diagnostics.jira.status].color"
|
|
336
|
+
variant="subtle"
|
|
337
|
+
:icon="STATUS_UI[tasks.diagnostics.jira.status].icon"
|
|
338
|
+
:description="
|
|
339
|
+
tasks.diagnostics.jira.message +
|
|
340
|
+
(tasks.diagnostics.jira.detail ? ` ${tasks.diagnostics.jira.detail}` : '')
|
|
341
|
+
"
|
|
342
|
+
:ui="{ description: 'text-[11px]' }"
|
|
343
|
+
/>
|
|
344
|
+
</div>
|
|
345
|
+
</section>
|
|
346
|
+
|
|
347
|
+
<!-- 3. Writeback ---------------------------------------------------------->
|
|
348
|
+
<section class="space-y-3">
|
|
349
|
+
<div>
|
|
350
|
+
<h3 class="text-sm font-semibold text-slate-200">Writeback</h3>
|
|
351
|
+
<p class="mt-1 text-[11px] text-slate-400">
|
|
352
|
+
Write back to a task's linked issue(s) as its pull request progresses. Each toggle is the
|
|
353
|
+
workspace default and can be overridden per task in the inspector. GitHub issues close
|
|
354
|
+
natively; Jira issues transition to the first status in their
|
|
355
|
+
<span class="text-slate-300">Done</span> category.
|
|
356
|
+
</p>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
360
|
+
<USwitch v-model="commentOnPrOpen" />
|
|
361
|
+
<span class="text-sm">
|
|
362
|
+
<span class="block text-slate-200">Comment when a PR opens</span>
|
|
363
|
+
<span class="block text-xs text-slate-500">
|
|
364
|
+
Post a comment on the linked issue with the new pull request's link.
|
|
365
|
+
</span>
|
|
366
|
+
</span>
|
|
367
|
+
</label>
|
|
368
|
+
|
|
369
|
+
<label class="flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-3">
|
|
370
|
+
<USwitch v-model="resolveOnMerge" />
|
|
371
|
+
<span class="text-sm">
|
|
372
|
+
<span class="block text-slate-200">Close as resolved when a PR merges</span>
|
|
373
|
+
<span class="block text-xs text-slate-500">
|
|
374
|
+
Comment that the PR merged, then close / resolve the linked issue.
|
|
375
|
+
</span>
|
|
376
|
+
</span>
|
|
377
|
+
</label>
|
|
378
|
+
</section>
|
|
379
|
+
|
|
380
|
+
<div class="flex justify-end">
|
|
381
|
+
<UButton
|
|
382
|
+
color="primary"
|
|
383
|
+
icon="i-lucide-save"
|
|
384
|
+
size="sm"
|
|
385
|
+
:loading="saving"
|
|
386
|
+
:disabled="!canSave"
|
|
387
|
+
@click="save"
|
|
388
|
+
>
|
|
389
|
+
Save
|
|
390
|
+
</UButton>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
</template>
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
// configuration that used to live in separate windows:
|
|
4
4
|
// - Workspace: the run-timing escalation threshold + per-service running-task limit.
|
|
5
5
|
// - Merge thresholds: the auto-merge preset library.
|
|
6
|
-
// - Issue
|
|
6
|
+
// - Issue tracker: filing-tracker selection + linking sources + writeback.
|
|
7
7
|
// - Service best practices: the default fragments new services inherit.
|
|
8
8
|
// The latter three are body-only section components rendered in tabs here (no longer
|
|
9
9
|
// standalone modals).
|
|
10
10
|
import { reactive, ref, watch } from 'vue'
|
|
11
11
|
import type { CreateTaskType, TaskLimitMode } from '~/types/domain'
|
|
12
12
|
import MergeThresholdsPanel from '~/components/settings/MergeThresholdsPanel.vue'
|
|
13
|
-
import
|
|
13
|
+
import IssueTrackerPanel from '~/components/settings/IssueTrackerPanel.vue'
|
|
14
14
|
import ServiceFragmentDefaultsPanel from '~/components/settings/ServiceFragmentDefaultsPanel.vue'
|
|
15
15
|
|
|
16
16
|
const ui = useUiStore()
|
|
@@ -38,10 +38,10 @@ const tabs = [
|
|
|
38
38
|
},
|
|
39
39
|
{ value: 'merge', label: 'Merge thresholds', icon: 'i-lucide-git-merge', slot: 'merge' },
|
|
40
40
|
{
|
|
41
|
-
value: '
|
|
42
|
-
label: 'Issue
|
|
43
|
-
icon: 'i-lucide-
|
|
44
|
-
slot: '
|
|
41
|
+
value: 'tracker',
|
|
42
|
+
label: 'Issue tracker',
|
|
43
|
+
icon: 'i-lucide-list-checks',
|
|
44
|
+
slot: 'tracker',
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
value: 'fragments',
|
|
@@ -200,9 +200,9 @@ async function save() {
|
|
|
200
200
|
<MergeThresholdsPanel />
|
|
201
201
|
</template>
|
|
202
202
|
|
|
203
|
-
<!-- Issue
|
|
204
|
-
<template #
|
|
205
|
-
<
|
|
203
|
+
<!-- Issue tracker -->
|
|
204
|
+
<template #tracker>
|
|
205
|
+
<IssueTrackerPanel />
|
|
206
206
|
</template>
|
|
207
207
|
|
|
208
208
|
<!-- Service best practices -->
|