@cat-factory/app 0.6.0 → 0.7.2
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/LICENSE +21 -21
- package/app/components/board/ContextPicker.vue +367 -367
- package/app/components/gates/GateResultView.vue +109 -4
- package/app/components/layout/SideBar.vue +11 -0
- package/app/components/observability/StepMetricsBar.vue +102 -102
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -178
- package/app/components/panels/inspector/TaskRunSettings.vue +77 -0
- package/app/components/recurring/RecurrenceEditor.vue +124 -124
- package/app/components/settings/IssueTrackerWritebackPanel.vue +103 -0
- package/app/composables/useBlockQueries.ts +154 -154
- package/app/composables/useContextLinking.ts +65 -65
- package/app/composables/useFrameResize.ts +54 -54
- package/app/pages/index.vue +2 -0
- package/app/stores/documents.ts +176 -176
- package/app/stores/services.ts +87 -87
- package/app/stores/tracker.ts +39 -27
- package/app/stores/ui.ts +12 -0
- package/app/types/documents.ts +104 -104
- package/app/types/domain.ts +5 -1
- package/app/types/execution.ts +18 -0
- package/app/types/github.ts +173 -173
- package/app/types/services.ts +27 -27
- package/app/types/tasks.ts +82 -82
- package/app/types/tracker.ts +27 -18
- package/app/utils/agentOutput.spec.ts +128 -128
- package/app/utils/agentOutput.ts +173 -173
- package/app/utils/observability.ts +52 -52
- package/package.json +6 -1
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// Inspector section shown when the selected task block backs a recurring pipeline.
|
|
3
|
-
// Lets the user edit the cadence, pause/resume, run now, and review the run history
|
|
4
|
-
// (lazily loaded; retained ~1 week on the backend).
|
|
5
|
-
import type { Block } from '~/types/domain'
|
|
6
|
-
import type { Recurrence } from '~/types/recurring'
|
|
7
|
-
|
|
8
|
-
const props = defineProps<{ block: Block }>()
|
|
9
|
-
const recurring = useRecurringPipelinesStore()
|
|
10
|
-
const pipelines = usePipelinesStore()
|
|
11
|
-
const toast = useToast()
|
|
12
|
-
|
|
13
|
-
const schedule = computed(() => recurring.byBlock(props.block.id))
|
|
14
|
-
const runs = computed(() =>
|
|
15
|
-
schedule.value ? (recurring.runsBySchedule[schedule.value.id] ?? []) : [],
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
const editing = ref(false)
|
|
19
|
-
const draft = ref<Recurrence | null>(null)
|
|
20
|
-
const busy = ref(false)
|
|
21
|
-
|
|
22
|
-
// Load history whenever a schedule is shown.
|
|
23
|
-
watch(
|
|
24
|
-
() => schedule.value?.id,
|
|
25
|
-
(id) => {
|
|
26
|
-
if (id) recurring.loadRuns(id).catch(() => {})
|
|
27
|
-
},
|
|
28
|
-
{ immediate: true },
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
const pipelineName = computed(
|
|
32
|
-
() => pipelines.getPipeline(schedule.value?.pipelineId ?? '')?.name ?? schedule.value?.pipelineId,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
function describeCadence(r: Recurrence): string {
|
|
36
|
-
const every = r.intervalHours % 24 === 0 ? `${r.intervalHours / 24}d` : `${r.intervalHours}h`
|
|
37
|
-
const days =
|
|
38
|
-
r.weekdays.length === 0
|
|
39
|
-
? 'any day'
|
|
40
|
-
: r.weekdays.map((d) => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d]).join(' ')
|
|
41
|
-
const window =
|
|
42
|
-
r.windowStartHour === null && r.windowEndHour === null
|
|
43
|
-
? ''
|
|
44
|
-
: ` · ${String(r.windowStartHour ?? 0).padStart(2, '0')}:00–${String(r.windowEndHour ?? 24).padStart(2, '0')}:00`
|
|
45
|
-
return `Every ${every} · ${days}${window} · ${r.timezone}`
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function startEdit() {
|
|
49
|
-
if (!schedule.value) return
|
|
50
|
-
draft.value = { ...schedule.value.recurrence }
|
|
51
|
-
editing.value = true
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function saveEdit() {
|
|
55
|
-
if (!schedule.value || !draft.value) return
|
|
56
|
-
busy.value = true
|
|
57
|
-
try {
|
|
58
|
-
await recurring.update(schedule.value.id, { recurrence: draft.value })
|
|
59
|
-
editing.value = false
|
|
60
|
-
} catch (e) {
|
|
61
|
-
toast.add({ title: 'Could not update schedule', description: errMsg(e), color: 'error' })
|
|
62
|
-
} finally {
|
|
63
|
-
busy.value = false
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function toggleEnabled() {
|
|
68
|
-
if (!schedule.value) return
|
|
69
|
-
busy.value = true
|
|
70
|
-
try {
|
|
71
|
-
await recurring.update(schedule.value.id, { enabled: !schedule.value.enabled })
|
|
72
|
-
} catch (e) {
|
|
73
|
-
toast.add({ title: 'Could not update schedule', description: errMsg(e), color: 'error' })
|
|
74
|
-
} finally {
|
|
75
|
-
busy.value = false
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function runNow() {
|
|
80
|
-
if (!schedule.value) return
|
|
81
|
-
busy.value = true
|
|
82
|
-
try {
|
|
83
|
-
await recurring.runNow(schedule.value.id)
|
|
84
|
-
} catch (e) {
|
|
85
|
-
toast.add({ title: 'Could not run now', description: errMsg(e), color: 'error' })
|
|
86
|
-
} finally {
|
|
87
|
-
busy.value = false
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function errMsg(e: unknown) {
|
|
92
|
-
return e instanceof Error ? e.message : String(e)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const RUN_COLOR: Record<string, string> = {
|
|
96
|
-
running: 'text-amber-400',
|
|
97
|
-
done: 'text-emerald-400',
|
|
98
|
-
failed: 'text-rose-400',
|
|
99
|
-
skipped: 'text-slate-500',
|
|
100
|
-
}
|
|
101
|
-
function fmtTime(ms: number) {
|
|
102
|
-
return new Date(ms).toLocaleString()
|
|
103
|
-
}
|
|
104
|
-
</script>
|
|
105
|
-
|
|
106
|
-
<template>
|
|
107
|
-
<div
|
|
108
|
-
v-if="schedule"
|
|
109
|
-
class="space-y-2 rounded-lg border border-indigo-900/50 bg-indigo-950/20 p-3"
|
|
110
|
-
>
|
|
111
|
-
<div class="flex items-center justify-between">
|
|
112
|
-
<span
|
|
113
|
-
class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-indigo-300"
|
|
114
|
-
>
|
|
115
|
-
<UIcon name="i-lucide-repeat" class="h-3.5 w-3.5" />
|
|
116
|
-
Recurring pipeline
|
|
117
|
-
</span>
|
|
118
|
-
<UBadge :color="schedule.enabled ? 'primary' : 'neutral'" variant="subtle" size="xs">
|
|
119
|
-
{{ schedule.enabled ? 'Active' : 'Paused' }}
|
|
120
|
-
</UBadge>
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
<p class="text-[11px] text-slate-400">
|
|
124
|
-
<span class="text-slate-300">{{ pipelineName }}</span>
|
|
125
|
-
</p>
|
|
126
|
-
|
|
127
|
-
<template v-if="!editing">
|
|
128
|
-
<p class="text-[11px] text-slate-400">{{ describeCadence(schedule.recurrence) }}</p>
|
|
129
|
-
<p class="text-[11px] text-slate-500">Next run: {{ fmtTime(schedule.nextRunAt) }}</p>
|
|
130
|
-
<div class="flex flex-wrap gap-1.5 pt-1">
|
|
131
|
-
<UButton size="xs" variant="soft" icon="i-lucide-play" :loading="busy" @click="runNow">
|
|
132
|
-
Run now
|
|
133
|
-
</UButton>
|
|
134
|
-
<UButton
|
|
135
|
-
size="xs"
|
|
136
|
-
variant="soft"
|
|
137
|
-
color="neutral"
|
|
138
|
-
:icon="schedule.enabled ? 'i-lucide-pause' : 'i-lucide-play'"
|
|
139
|
-
:loading="busy"
|
|
140
|
-
@click="toggleEnabled"
|
|
141
|
-
>
|
|
142
|
-
{{ schedule.enabled ? 'Pause' : 'Resume' }}
|
|
143
|
-
</UButton>
|
|
144
|
-
<UButton
|
|
145
|
-
size="xs"
|
|
146
|
-
variant="ghost"
|
|
147
|
-
color="neutral"
|
|
148
|
-
icon="i-lucide-pencil"
|
|
149
|
-
@click="startEdit"
|
|
150
|
-
>
|
|
151
|
-
Edit cadence
|
|
152
|
-
</UButton>
|
|
153
|
-
</div>
|
|
154
|
-
</template>
|
|
155
|
-
|
|
156
|
-
<template v-else-if="draft">
|
|
157
|
-
<RecurringRecurrenceEditor v-model="draft" />
|
|
158
|
-
<div class="flex justify-end gap-1.5 pt-1">
|
|
159
|
-
<UButton size="xs" variant="ghost" color="neutral" @click="editing = false">Cancel</UButton>
|
|
160
|
-
<UButton size="xs" color="primary" :loading="busy" @click="saveEdit">Save</UButton>
|
|
161
|
-
</div>
|
|
162
|
-
</template>
|
|
163
|
-
|
|
164
|
-
<!-- run history -->
|
|
165
|
-
<div v-if="runs.length" class="space-y-1 border-t border-slate-800 pt-2">
|
|
166
|
-
<span class="text-[10px] font-semibold uppercase tracking-wide text-slate-500">
|
|
167
|
-
Recent runs
|
|
168
|
-
</span>
|
|
169
|
-
<div v-for="run in runs" :key="run.id" class="flex items-center gap-2 text-[11px]">
|
|
170
|
-
<span :class="RUN_COLOR[run.status] ?? 'text-slate-400'" class="w-14 shrink-0 capitalize">
|
|
171
|
-
{{ run.status }}
|
|
172
|
-
</span>
|
|
173
|
-
<span class="truncate text-slate-500">{{ fmtTime(run.startedAt) }}</span>
|
|
174
|
-
<span v-if="run.outcome" class="ml-auto truncate text-slate-500">{{ run.outcome }}</span>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Inspector section shown when the selected task block backs a recurring pipeline.
|
|
3
|
+
// Lets the user edit the cadence, pause/resume, run now, and review the run history
|
|
4
|
+
// (lazily loaded; retained ~1 week on the backend).
|
|
5
|
+
import type { Block } from '~/types/domain'
|
|
6
|
+
import type { Recurrence } from '~/types/recurring'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{ block: Block }>()
|
|
9
|
+
const recurring = useRecurringPipelinesStore()
|
|
10
|
+
const pipelines = usePipelinesStore()
|
|
11
|
+
const toast = useToast()
|
|
12
|
+
|
|
13
|
+
const schedule = computed(() => recurring.byBlock(props.block.id))
|
|
14
|
+
const runs = computed(() =>
|
|
15
|
+
schedule.value ? (recurring.runsBySchedule[schedule.value.id] ?? []) : [],
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const editing = ref(false)
|
|
19
|
+
const draft = ref<Recurrence | null>(null)
|
|
20
|
+
const busy = ref(false)
|
|
21
|
+
|
|
22
|
+
// Load history whenever a schedule is shown.
|
|
23
|
+
watch(
|
|
24
|
+
() => schedule.value?.id,
|
|
25
|
+
(id) => {
|
|
26
|
+
if (id) recurring.loadRuns(id).catch(() => {})
|
|
27
|
+
},
|
|
28
|
+
{ immediate: true },
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const pipelineName = computed(
|
|
32
|
+
() => pipelines.getPipeline(schedule.value?.pipelineId ?? '')?.name ?? schedule.value?.pipelineId,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
function describeCadence(r: Recurrence): string {
|
|
36
|
+
const every = r.intervalHours % 24 === 0 ? `${r.intervalHours / 24}d` : `${r.intervalHours}h`
|
|
37
|
+
const days =
|
|
38
|
+
r.weekdays.length === 0
|
|
39
|
+
? 'any day'
|
|
40
|
+
: r.weekdays.map((d) => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d]).join(' ')
|
|
41
|
+
const window =
|
|
42
|
+
r.windowStartHour === null && r.windowEndHour === null
|
|
43
|
+
? ''
|
|
44
|
+
: ` · ${String(r.windowStartHour ?? 0).padStart(2, '0')}:00–${String(r.windowEndHour ?? 24).padStart(2, '0')}:00`
|
|
45
|
+
return `Every ${every} · ${days}${window} · ${r.timezone}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function startEdit() {
|
|
49
|
+
if (!schedule.value) return
|
|
50
|
+
draft.value = { ...schedule.value.recurrence }
|
|
51
|
+
editing.value = true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function saveEdit() {
|
|
55
|
+
if (!schedule.value || !draft.value) return
|
|
56
|
+
busy.value = true
|
|
57
|
+
try {
|
|
58
|
+
await recurring.update(schedule.value.id, { recurrence: draft.value })
|
|
59
|
+
editing.value = false
|
|
60
|
+
} catch (e) {
|
|
61
|
+
toast.add({ title: 'Could not update schedule', description: errMsg(e), color: 'error' })
|
|
62
|
+
} finally {
|
|
63
|
+
busy.value = false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function toggleEnabled() {
|
|
68
|
+
if (!schedule.value) return
|
|
69
|
+
busy.value = true
|
|
70
|
+
try {
|
|
71
|
+
await recurring.update(schedule.value.id, { enabled: !schedule.value.enabled })
|
|
72
|
+
} catch (e) {
|
|
73
|
+
toast.add({ title: 'Could not update schedule', description: errMsg(e), color: 'error' })
|
|
74
|
+
} finally {
|
|
75
|
+
busy.value = false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function runNow() {
|
|
80
|
+
if (!schedule.value) return
|
|
81
|
+
busy.value = true
|
|
82
|
+
try {
|
|
83
|
+
await recurring.runNow(schedule.value.id)
|
|
84
|
+
} catch (e) {
|
|
85
|
+
toast.add({ title: 'Could not run now', description: errMsg(e), color: 'error' })
|
|
86
|
+
} finally {
|
|
87
|
+
busy.value = false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function errMsg(e: unknown) {
|
|
92
|
+
return e instanceof Error ? e.message : String(e)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const RUN_COLOR: Record<string, string> = {
|
|
96
|
+
running: 'text-amber-400',
|
|
97
|
+
done: 'text-emerald-400',
|
|
98
|
+
failed: 'text-rose-400',
|
|
99
|
+
skipped: 'text-slate-500',
|
|
100
|
+
}
|
|
101
|
+
function fmtTime(ms: number) {
|
|
102
|
+
return new Date(ms).toLocaleString()
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<div
|
|
108
|
+
v-if="schedule"
|
|
109
|
+
class="space-y-2 rounded-lg border border-indigo-900/50 bg-indigo-950/20 p-3"
|
|
110
|
+
>
|
|
111
|
+
<div class="flex items-center justify-between">
|
|
112
|
+
<span
|
|
113
|
+
class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-indigo-300"
|
|
114
|
+
>
|
|
115
|
+
<UIcon name="i-lucide-repeat" class="h-3.5 w-3.5" />
|
|
116
|
+
Recurring pipeline
|
|
117
|
+
</span>
|
|
118
|
+
<UBadge :color="schedule.enabled ? 'primary' : 'neutral'" variant="subtle" size="xs">
|
|
119
|
+
{{ schedule.enabled ? 'Active' : 'Paused' }}
|
|
120
|
+
</UBadge>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<p class="text-[11px] text-slate-400">
|
|
124
|
+
<span class="text-slate-300">{{ pipelineName }}</span>
|
|
125
|
+
</p>
|
|
126
|
+
|
|
127
|
+
<template v-if="!editing">
|
|
128
|
+
<p class="text-[11px] text-slate-400">{{ describeCadence(schedule.recurrence) }}</p>
|
|
129
|
+
<p class="text-[11px] text-slate-500">Next run: {{ fmtTime(schedule.nextRunAt) }}</p>
|
|
130
|
+
<div class="flex flex-wrap gap-1.5 pt-1">
|
|
131
|
+
<UButton size="xs" variant="soft" icon="i-lucide-play" :loading="busy" @click="runNow">
|
|
132
|
+
Run now
|
|
133
|
+
</UButton>
|
|
134
|
+
<UButton
|
|
135
|
+
size="xs"
|
|
136
|
+
variant="soft"
|
|
137
|
+
color="neutral"
|
|
138
|
+
:icon="schedule.enabled ? 'i-lucide-pause' : 'i-lucide-play'"
|
|
139
|
+
:loading="busy"
|
|
140
|
+
@click="toggleEnabled"
|
|
141
|
+
>
|
|
142
|
+
{{ schedule.enabled ? 'Pause' : 'Resume' }}
|
|
143
|
+
</UButton>
|
|
144
|
+
<UButton
|
|
145
|
+
size="xs"
|
|
146
|
+
variant="ghost"
|
|
147
|
+
color="neutral"
|
|
148
|
+
icon="i-lucide-pencil"
|
|
149
|
+
@click="startEdit"
|
|
150
|
+
>
|
|
151
|
+
Edit cadence
|
|
152
|
+
</UButton>
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<template v-else-if="draft">
|
|
157
|
+
<RecurringRecurrenceEditor v-model="draft" />
|
|
158
|
+
<div class="flex justify-end gap-1.5 pt-1">
|
|
159
|
+
<UButton size="xs" variant="ghost" color="neutral" @click="editing = false">Cancel</UButton>
|
|
160
|
+
<UButton size="xs" color="primary" :loading="busy" @click="saveEdit">Save</UButton>
|
|
161
|
+
</div>
|
|
162
|
+
</template>
|
|
163
|
+
|
|
164
|
+
<!-- run history -->
|
|
165
|
+
<div v-if="runs.length" class="space-y-1 border-t border-slate-800 pt-2">
|
|
166
|
+
<span class="text-[10px] font-semibold uppercase tracking-wide text-slate-500">
|
|
167
|
+
Recent runs
|
|
168
|
+
</span>
|
|
169
|
+
<div v-for="run in runs" :key="run.id" class="flex items-center gap-2 text-[11px]">
|
|
170
|
+
<span :class="RUN_COLOR[run.status] ?? 'text-slate-400'" class="w-14 shrink-0 capitalize">
|
|
171
|
+
{{ run.status }}
|
|
172
|
+
</span>
|
|
173
|
+
<span class="truncate text-slate-500">{{ fmtTime(run.startedAt) }}</span>
|
|
174
|
+
<span v-if="run.outcome" class="ml-auto truncate text-slate-500">{{ run.outcome }}</span>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, onMounted } from 'vue'
|
|
3
3
|
import type { Block } from '~/types/domain'
|
|
4
|
+
import type { WritebackOverride } from '~/types/tracker'
|
|
4
5
|
|
|
5
6
|
const props = defineProps<{ block: Block }>()
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ const board = useBoardStore()
|
|
|
8
9
|
const mergePresets = useMergePresetsStore()
|
|
9
10
|
const pipelines = usePipelinesStore()
|
|
10
11
|
const accounts = useAccountsStore()
|
|
12
|
+
const tracker = useTrackerStore()
|
|
11
13
|
|
|
12
14
|
// ---- responsible product person --------------------------------------------
|
|
13
15
|
// The account member (a `product` role-holder) accountable for this task; they are
|
|
@@ -87,6 +89,41 @@ const pipelineMenu = computed(() => [
|
|
|
87
89
|
function setPipeline(id: string) {
|
|
88
90
|
board.updateBlock(props.block.id, { pipelineId: id })
|
|
89
91
|
}
|
|
92
|
+
|
|
93
|
+
// ---- issue-tracker writeback overrides -------------------------------------
|
|
94
|
+
// Per-task overrides for the two workspace writeback toggles (comment on PR open,
|
|
95
|
+
// close linked issue on merge). null override ⇒ inherit the workspace default.
|
|
96
|
+
function setCommentOnPrOpen(value: WritebackOverride | null) {
|
|
97
|
+
board.updateBlock(props.block.id, { trackerCommentOnPrOpen: value })
|
|
98
|
+
}
|
|
99
|
+
function setResolveOnMerge(value: WritebackOverride | null) {
|
|
100
|
+
board.updateBlock(props.block.id, { trackerResolveOnMerge: value })
|
|
101
|
+
}
|
|
102
|
+
function writebackMenu(set: (value: WritebackOverride | null) => void) {
|
|
103
|
+
return [
|
|
104
|
+
[
|
|
105
|
+
{ label: 'Inherit workspace', icon: 'i-lucide-rotate-ccw', onSelect: () => set(null) },
|
|
106
|
+
{ label: 'On', icon: 'i-lucide-check', onSelect: () => set('on') },
|
|
107
|
+
{ label: 'Off', icon: 'i-lucide-x', onSelect: () => set('off') },
|
|
108
|
+
],
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writebackLabel(
|
|
113
|
+
override: WritebackOverride | null | undefined,
|
|
114
|
+
wsDefault: boolean,
|
|
115
|
+
): string {
|
|
116
|
+
if (override === 'on') return 'On'
|
|
117
|
+
if (override === 'off') return 'Off'
|
|
118
|
+
return `Inherit (${wsDefault ? 'on' : 'off'})`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const commentOnPrOpenLabel = computed(() =>
|
|
122
|
+
writebackLabel(props.block.trackerCommentOnPrOpen, tracker.settings.writebackCommentOnPrOpen),
|
|
123
|
+
)
|
|
124
|
+
const resolveOnMergeLabel = computed(() =>
|
|
125
|
+
writebackLabel(props.block.trackerResolveOnMerge, tracker.settings.writebackResolveOnMerge),
|
|
126
|
+
)
|
|
90
127
|
</script>
|
|
91
128
|
|
|
92
129
|
<template>
|
|
@@ -152,6 +189,46 @@ function setPipeline(id: string) {
|
|
|
152
189
|
</div>
|
|
153
190
|
</div>
|
|
154
191
|
|
|
192
|
+
<!-- issue-tracker writeback overrides -->
|
|
193
|
+
<div>
|
|
194
|
+
<div class="mb-1 flex items-center justify-between">
|
|
195
|
+
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
196
|
+
Issue writeback
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="space-y-1.5">
|
|
200
|
+
<div class="flex items-center justify-between">
|
|
201
|
+
<span class="text-[11px] text-slate-400">Comment on PR open</span>
|
|
202
|
+
<UDropdownMenu :items="writebackMenu(setCommentOnPrOpen)">
|
|
203
|
+
<UButton
|
|
204
|
+
size="xs"
|
|
205
|
+
variant="ghost"
|
|
206
|
+
color="neutral"
|
|
207
|
+
trailing-icon="i-lucide-chevron-down"
|
|
208
|
+
>
|
|
209
|
+
{{ commentOnPrOpenLabel }}
|
|
210
|
+
</UButton>
|
|
211
|
+
</UDropdownMenu>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="flex items-center justify-between">
|
|
214
|
+
<span class="text-[11px] text-slate-400">Close on merge</span>
|
|
215
|
+
<UDropdownMenu :items="writebackMenu(setResolveOnMerge)">
|
|
216
|
+
<UButton
|
|
217
|
+
size="xs"
|
|
218
|
+
variant="ghost"
|
|
219
|
+
color="neutral"
|
|
220
|
+
trailing-icon="i-lucide-chevron-down"
|
|
221
|
+
>
|
|
222
|
+
{{ resolveOnMergeLabel }}
|
|
223
|
+
</UButton>
|
|
224
|
+
</UDropdownMenu>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="mt-1 text-[11px] text-slate-500">
|
|
228
|
+
Writes back to this task's linked tracker issue. Overrides the workspace default.
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
155
232
|
<!-- responsible product person -->
|
|
156
233
|
<div>
|
|
157
234
|
<div class="mb-1 flex items-center justify-between">
|