@cat-factory/app 0.271.0 → 0.272.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/tasks/BugHuntModal.logic.spec.ts +83 -0
- package/app/components/tasks/BugHuntModal.logic.ts +54 -0
- package/app/components/tasks/BugHuntModal.vue +149 -25
- package/app/stores/bugHunt.spec.ts +122 -1
- package/app/stores/bugHunt.ts +35 -1
- package/app/stores/tasks.spec.ts +2 -0
- package/app/utils/intakePredicates.spec.ts +1 -0
- package/i18n/locales/de.json +10 -1
- package/i18n/locales/en.json +10 -1
- package/i18n/locales/es.json +10 -1
- package/i18n/locales/fr.json +10 -1
- package/i18n/locales/he.json +10 -1
- package/i18n/locales/it.json +10 -1
- package/i18n/locales/ja.json +10 -1
- package/i18n/locales/pl.json +10 -1
- package/i18n/locales/tr.json +10 -1
- package/i18n/locales/uk.json +10 -1
- package/package.json +4 -4
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import type { TaskSourceState } from '@cat-factory/contracts'
|
|
3
|
+
import { boardFromService, huntRequest } from './BugHuntModal.logic'
|
|
4
|
+
|
|
5
|
+
// What scopes a hunt. The rule is small and the failures are silent in both directions: a
|
|
6
|
+
// repo-backed tracker that sends a board would be refused on submit, and one that sent an empty
|
|
7
|
+
// string instead of an explicit null would read as a board named as blank.
|
|
8
|
+
|
|
9
|
+
function state(overrides: Partial<TaskSourceState> = {}): TaskSourceState {
|
|
10
|
+
return {
|
|
11
|
+
source: 'github',
|
|
12
|
+
label: 'GitHub Issues',
|
|
13
|
+
icon: 'i-lucide-github',
|
|
14
|
+
credentialFields: [],
|
|
15
|
+
refLabel: 'Issue URL',
|
|
16
|
+
refPlaceholder: 'acme/web#123',
|
|
17
|
+
available: true,
|
|
18
|
+
enabled: true,
|
|
19
|
+
ridesVcsProvider: 'github',
|
|
20
|
+
supportsIntake: true,
|
|
21
|
+
ignoredIntakePredicates: [],
|
|
22
|
+
repoBacked: true,
|
|
23
|
+
...overrides,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FORM = { containerId: 'blk-1', board: '', issueType: '', labels: '' }
|
|
28
|
+
|
|
29
|
+
describe('boardFromService', () => {
|
|
30
|
+
it('follows what the SOURCE declares, not which source it is', () => {
|
|
31
|
+
expect(boardFromService(state({ source: 'acme:forge' }))).toBe(true)
|
|
32
|
+
expect(boardFromService(state({ source: 'jira', repoBacked: false }))).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('treats an unresolved source as having a board, so a control is still rendered', () => {
|
|
36
|
+
expect(boardFromService(undefined)).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('huntRequest', () => {
|
|
41
|
+
it('sends an explicit null board for a repo-backed tracker, whatever was typed before', () => {
|
|
42
|
+
const request = huntRequest({ ...FORM, source: state(), board: 'someone-else/web' })
|
|
43
|
+
|
|
44
|
+
// Null, never '' and never the stale text: the backend REFUSES a board named for such a
|
|
45
|
+
// source, so a hunt that carried one would be rejected rather than scoped.
|
|
46
|
+
expect(request).toEqual({ containerId: 'blk-1', board: null })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('sends the trimmed board a repo-less tracker names', () => {
|
|
50
|
+
const source = state({ source: 'jira', repoBacked: false })
|
|
51
|
+
|
|
52
|
+
expect(huntRequest({ ...FORM, source, board: ' PROJ ' })).toEqual({
|
|
53
|
+
containerId: 'blk-1',
|
|
54
|
+
board: 'PROJ',
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('describes no scan until a repo-less tracker has a board', () => {
|
|
59
|
+
const source = state({ source: 'jira', repoBacked: false })
|
|
60
|
+
|
|
61
|
+
expect(huntRequest({ ...FORM, source, board: ' ' })).toBeNull()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('describes no scan without a container, which decides the repository too', () => {
|
|
65
|
+
expect(huntRequest({ ...FORM, source: state(), containerId: undefined })).toBeNull()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('carries only the predicates that were actually filled in', () => {
|
|
69
|
+
const request = huntRequest({
|
|
70
|
+
...FORM,
|
|
71
|
+
source: state(),
|
|
72
|
+
issueType: ' defect ',
|
|
73
|
+
labels: 'regression, , checkout ',
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
expect(request).toEqual({
|
|
77
|
+
containerId: 'blk-1',
|
|
78
|
+
board: null,
|
|
79
|
+
issueType: 'defect',
|
|
80
|
+
labels: ['regression', 'checkout'],
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { RunBugHuntInput, TaskSourceState } from '@cat-factory/contracts'
|
|
2
|
+
|
|
3
|
+
// The pure half of BugHuntModal: what SCOPES a hunt. Extracted for the reason every `*.logic.ts`
|
|
4
|
+
// here is (a decision worth a test should not need a mounted component to reach), and this one
|
|
5
|
+
// carries the whole rule the surface exists to enforce: a repo-backed tracker hunts the
|
|
6
|
+
// repository of the service the bug will land in, and names no board of its own.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether this tracker's board is the chosen service's repository rather than a choice.
|
|
10
|
+
*
|
|
11
|
+
* Read off the source's declared `repoBacked`, never its id: a deployment that registers its own
|
|
12
|
+
* repo-backed source, or one running GitLab instead of GitHub, must behave identically, and a
|
|
13
|
+
* source list compared here would be a second authority that drifts from the backend's.
|
|
14
|
+
* An unresolved source (still loading, or one this workspace no longer offers) is NOT repo-backed:
|
|
15
|
+
* the answer decides which control to render, and rendering none is the option a user cannot
|
|
16
|
+
* correct.
|
|
17
|
+
*/
|
|
18
|
+
export function boardFromService(source: TaskSourceState | undefined): boolean {
|
|
19
|
+
return source?.repoBacked === true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The scan request, or null when the form does not yet name one.
|
|
24
|
+
*
|
|
25
|
+
* `board` is explicitly `null` for a repo-backed tracker rather than an empty string: the backend
|
|
26
|
+
* REFUSES a board named for such a source instead of ignoring it, so the difference between "no
|
|
27
|
+
* board to name" and "a board named as blank" has to survive this far. The container is required
|
|
28
|
+
* either way: it is where an adopted bug lands, and on a repo-backed tracker it is also what
|
|
29
|
+
* decides which repository is read at all.
|
|
30
|
+
*/
|
|
31
|
+
export function huntRequest(input: {
|
|
32
|
+
source: TaskSourceState | undefined
|
|
33
|
+
containerId: string | undefined
|
|
34
|
+
board: string
|
|
35
|
+
issueType: string
|
|
36
|
+
labels: string
|
|
37
|
+
}): RunBugHuntInput | null {
|
|
38
|
+
const { containerId } = input
|
|
39
|
+
if (!input.source || !containerId) return null
|
|
40
|
+
const fromService = boardFromService(input.source)
|
|
41
|
+
const board = input.board.trim()
|
|
42
|
+
if (!fromService && !board) return null
|
|
43
|
+
const issueType = input.issueType.trim()
|
|
44
|
+
const labels = input.labels
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((label) => label.trim())
|
|
47
|
+
.filter((label) => label.length > 0)
|
|
48
|
+
return {
|
|
49
|
+
containerId,
|
|
50
|
+
board: fromService ? null : board,
|
|
51
|
+
...(issueType ? { issueType } : {}),
|
|
52
|
+
...(labels.length ? { labels } : {}),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// Bug hunt: pick a connected tracker,
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Bug hunt: pick a connected tracker, scope the scan, and let the platform rank that board's
|
|
3
|
+
// open, UNASSIGNED bugs by impact against implementation complexity. Confirming a candidate
|
|
4
|
+
// adopts it as a bug task in the chosen container and starts the bug-fix pipeline.
|
|
5
|
+
//
|
|
6
|
+
// What SCOPES the scan depends on the tracker, and the source states which (`repoBacked`): a
|
|
7
|
+
// repo-backed one (GitHub Issues, GitLab Issues) hunts the repository the chosen service is
|
|
8
|
+
// linked to and offers NO board control, because its issues live in one repo per service and the
|
|
9
|
+
// only honest answer is the one the backend resolves. Every other tracker names a board of its
|
|
10
|
+
// own. Never a picker either way that could aim a hunt at a repository this board holds no
|
|
11
|
+
// service for.
|
|
5
12
|
//
|
|
6
13
|
// The interactive dual of the recurring bug-triage schedule: same reading and same pipeline,
|
|
7
14
|
// but a human picks the bug instead of the oldest match being claimed unattended.
|
|
@@ -37,11 +44,14 @@ import {
|
|
|
37
44
|
} from '~/utils/sourcePicker'
|
|
38
45
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
39
46
|
import { appliesIntakePredicate } from '~/utils/intakePredicates'
|
|
47
|
+
import { boardFromService as isBoardFromService, huntRequest } from './BugHuntModal.logic'
|
|
40
48
|
|
|
41
49
|
const { t, d, n } = useI18n()
|
|
42
50
|
const ui = useUiStore()
|
|
43
51
|
const tasks = useTasksStore()
|
|
44
52
|
const hunt = useBugHuntStore()
|
|
53
|
+
const board = useBoardStore()
|
|
54
|
+
const github = useGitHubStore()
|
|
45
55
|
const toast = useToast()
|
|
46
56
|
const { present } = usePipelineErrorToast()
|
|
47
57
|
|
|
@@ -130,6 +140,36 @@ const boardItems = computed(() =>
|
|
|
130
140
|
})),
|
|
131
141
|
)
|
|
132
142
|
|
|
143
|
+
/** This tracker's board is the chosen service's own repository (see the logic module). */
|
|
144
|
+
const boardFromService = computed(() => isBoardFromService(descriptor.value))
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* WHICH repository a repo-backed hunt will read, named before it runs.
|
|
148
|
+
*
|
|
149
|
+
* The premise of this whole branch is that the platform picks the board on the user's behalf, so
|
|
150
|
+
* withholding the value until the results block (`scannedBoard`) states it only after a billable
|
|
151
|
+
* scan has already been paid for. Resolved the way the backend resolves it — walk the chosen
|
|
152
|
+
* container up to its service frame, read that frame's repo link — so the two cannot name
|
|
153
|
+
* different repositories. Null while the projection is still loading or the service holds no
|
|
154
|
+
* link; the field then says what it always said, and the not-linked case stays the backend's to
|
|
155
|
+
* refuse (`boardNeedsRepo`), since an unloaded projection and an unlinked service look identical
|
|
156
|
+
* from here.
|
|
157
|
+
*/
|
|
158
|
+
const scopedRepo = computed(() => {
|
|
159
|
+
const container = containerId.value ? board.getBlock(containerId.value) : undefined
|
|
160
|
+
const frame = container ? board.serviceOf(container) : undefined
|
|
161
|
+
const repo = frame ? github.repoForBlock(frame.id) : undefined
|
|
162
|
+
return repo ? `${repo.owner}/${repo.name}` : null
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The service this hunt is scoped to has no repository linked, so it has no issues to read. The
|
|
167
|
+
* one scan failure worded here instead of in a toast: it names something to fix on this board,
|
|
168
|
+
* and it belongs beside the scope it invalidates.
|
|
169
|
+
*/
|
|
170
|
+
const REPO_NOT_LINKED: TaskSourceReadReason = 'repo_not_linked'
|
|
171
|
+
const huntNeedsRepo = computed(() => hunt.huntErrorReason === REPO_NOT_LINKED)
|
|
172
|
+
|
|
133
173
|
/**
|
|
134
174
|
* The tracker CANNOT enumerate boards, so the user types the scope in themselves. Keyed on the
|
|
135
175
|
* backend's reason code, not on "any error": an unreachable tracker or an expired token would
|
|
@@ -140,10 +180,30 @@ const boardIsFreeText = computed(
|
|
|
140
180
|
() => !hunt.boardsLoading && hunt.boardsErrorReason === BOARDS_UNSUPPORTED,
|
|
141
181
|
)
|
|
142
182
|
|
|
183
|
+
/**
|
|
184
|
+
* The refusals this surface words ITSELF, keyed on the backend's reason.
|
|
185
|
+
*
|
|
186
|
+
* The backend does not localize prose, so a reason with no entry here renders the server's
|
|
187
|
+
* untranslated English — the honest last resort for a cause this modal was not built to explain,
|
|
188
|
+
* and the wrong answer for one it was. Both entries are reachable only from a client that
|
|
189
|
+
* disagrees with the backend about which sources are repo-backed (a stale SPA build, a raced
|
|
190
|
+
* `repoBacked` read), which is exactly when a user is least served by raw backend prose.
|
|
191
|
+
* `repo_not_linked` is deliberately absent: it is not a message but a warning rendered beside the
|
|
192
|
+
* scope it invalidates.
|
|
193
|
+
*/
|
|
194
|
+
const REFUSAL_KEYS: Partial<Record<TaskSourceReadReason, string>> = {
|
|
195
|
+
board_from_service: 'bugHunt.refusal.boardFromService',
|
|
196
|
+
missing_board: 'bugHunt.refusal.missingBoard',
|
|
197
|
+
}
|
|
198
|
+
function refusalText(reason: string | null, fallback: string | null): string | null {
|
|
199
|
+
const key = reason ? REFUSAL_KEYS[reason as TaskSourceReadReason] : undefined
|
|
200
|
+
return key ? t(key) : fallback
|
|
201
|
+
}
|
|
202
|
+
|
|
143
203
|
/** A board read that failed for a reason the user has to fix — shown, never silently swallowed. */
|
|
144
204
|
const boardsFailure = computed(() =>
|
|
145
205
|
!hunt.boardsLoading && hunt.boardsError !== null && !boardIsFreeText.value
|
|
146
|
-
? hunt.boardsError
|
|
206
|
+
? refusalText(hunt.boardsErrorReason, hunt.boardsError)
|
|
147
207
|
: null,
|
|
148
208
|
)
|
|
149
209
|
|
|
@@ -157,7 +217,17 @@ function createdAtDate(createdAt: string): Date | null {
|
|
|
157
217
|
return Number.isNaN(parsed.getTime()) ? null : parsed
|
|
158
218
|
}
|
|
159
219
|
|
|
160
|
-
|
|
220
|
+
/** The scan this form currently describes, or null while it does not describe one. */
|
|
221
|
+
const request = computed(() =>
|
|
222
|
+
huntRequest({
|
|
223
|
+
source: descriptor.value,
|
|
224
|
+
containerId: containerId.value,
|
|
225
|
+
board: boardId.value,
|
|
226
|
+
issueType: issueType.value,
|
|
227
|
+
labels: labels.value,
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
const canHunt = computed(() => request.value !== null)
|
|
161
231
|
|
|
162
232
|
watch(open, (isOpen) => {
|
|
163
233
|
if (!isOpen) return
|
|
@@ -168,7 +238,7 @@ watch(open, (isOpen) => {
|
|
|
168
238
|
awaitingConnect.value = null
|
|
169
239
|
source.value = ui.bugHunt?.source ?? tasks.offeredSources[0]?.source ?? undefined
|
|
170
240
|
resetContainer()
|
|
171
|
-
|
|
241
|
+
loadBoardsFor(source.value)
|
|
172
242
|
})
|
|
173
243
|
|
|
174
244
|
// Switching tracker invalidates both the board list and any ranking already on screen: the
|
|
@@ -176,24 +246,40 @@ watch(open, (isOpen) => {
|
|
|
176
246
|
watch(source, (next) => {
|
|
177
247
|
boardId.value = ''
|
|
178
248
|
hunt.reset()
|
|
179
|
-
|
|
249
|
+
loadBoardsFor(next)
|
|
180
250
|
})
|
|
181
251
|
|
|
252
|
+
// For a repo-backed tracker the service IS the board, so moving the hunt to another service moves
|
|
253
|
+
// it to another repository: the shortlist on screen belongs to the old one and must go with it.
|
|
254
|
+
// A repo-less tracker keeps its results, since the container only decides where an adopted bug
|
|
255
|
+
// lands and the scan is still of the board that was asked for.
|
|
256
|
+
watch(containerId, () => {
|
|
257
|
+
if (boardFromService.value) hunt.reset()
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Boards are listed only for a tracker that HAS a board to choose; asking otherwise is refused
|
|
262
|
+
* server-side. The other branch is not a no-op: the previous tracker's list (or the warning its
|
|
263
|
+
* failed listing left behind) has to go, or it renders under a tracker with no board field.
|
|
264
|
+
*/
|
|
265
|
+
function loadBoardsFor(next: TaskSourceKind | undefined) {
|
|
266
|
+
if (!next) return
|
|
267
|
+
if (isBoardFromService(tasks.descriptorFor(next))) {
|
|
268
|
+
hunt.dropBoards(next)
|
|
269
|
+
// The repo projection is lazy and nothing on the board opens it, so the field that names the
|
|
270
|
+
// repository this hunt will read asks for it here — only on the branch that has one.
|
|
271
|
+
void github.ensureLoaded().catch(() => {})
|
|
272
|
+
} else hunt.loadBoards(next)
|
|
273
|
+
}
|
|
274
|
+
|
|
182
275
|
async function runHunt() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
.filter((l) => l.length > 0)
|
|
188
|
-
const ok = await hunt.hunt(source.value, {
|
|
189
|
-
board: boardId.value.trim(),
|
|
190
|
-
...(issueType.value.trim() ? { issueType: issueType.value.trim() } : {}),
|
|
191
|
-
...(parsedLabels.length ? { labels: parsedLabels } : {}),
|
|
192
|
-
})
|
|
193
|
-
if (!ok) {
|
|
276
|
+
const input = request.value
|
|
277
|
+
if (!source.value || !input) return
|
|
278
|
+
const ok = await hunt.hunt(source.value, input)
|
|
279
|
+
if (!ok && !huntNeedsRepo.value) {
|
|
194
280
|
toast.add({
|
|
195
281
|
title: t('bugHunt.huntFailed'),
|
|
196
|
-
description: hunt.huntError ?? undefined,
|
|
282
|
+
description: refusalText(hunt.huntErrorReason, hunt.huntError) ?? undefined,
|
|
197
283
|
icon: 'i-lucide-triangle-alert',
|
|
198
284
|
color: 'error',
|
|
199
285
|
})
|
|
@@ -306,10 +392,21 @@ const STATUS_KEYS: Record<BugHuntAnalysisStatus, string> = {
|
|
|
306
392
|
</UFormField>
|
|
307
393
|
|
|
308
394
|
<UFormField :label="t('bugHunt.board')">
|
|
395
|
+
<!-- This tracker's issues belong to one repository per service, so the board is
|
|
396
|
+
STATED rather than asked: the repository the service below is linked to. No
|
|
397
|
+
control at all, because every value one could offer here is either that repo
|
|
398
|
+
(nothing to choose) or another one this board holds no service for. -->
|
|
399
|
+
<p
|
|
400
|
+
v-if="boardFromService"
|
|
401
|
+
class="flex items-center gap-1.5 py-1 text-sm text-slate-300"
|
|
402
|
+
>
|
|
403
|
+
<UIcon name="i-lucide-folder-git-2" class="h-4 w-4 shrink-0" />
|
|
404
|
+
<span class="truncate">{{ scopedRepo ?? t('bugHunt.boardFromService') }}</span>
|
|
405
|
+
</p>
|
|
309
406
|
<!-- A tracker that can't enumerate its boards gets a free-text field rather than
|
|
310
407
|
an empty picker, so the hunt is still usable. -->
|
|
311
408
|
<UInput
|
|
312
|
-
v-if="boardIsFreeText"
|
|
409
|
+
v-else-if="boardIsFreeText"
|
|
313
410
|
v-model="boardId"
|
|
314
411
|
:placeholder="t('bugHunt.boardPlaceholder')"
|
|
315
412
|
class="w-full"
|
|
@@ -322,9 +419,14 @@ const STATUS_KEYS: Record<BugHuntAnalysisStatus, string> = {
|
|
|
322
419
|
:placeholder="t('bugHunt.pickBoard')"
|
|
323
420
|
class="w-full"
|
|
324
421
|
/>
|
|
422
|
+
<!-- The service holds no repository, so there are no issues to read. Said here
|
|
423
|
+
rather than in a toast: it invalidates the scope named right above it. -->
|
|
424
|
+
<p v-if="huntNeedsRepo" class="mt-1 text-xs text-amber-400">
|
|
425
|
+
{{ t('bugHunt.boardNeedsRepo') }}
|
|
426
|
+
</p>
|
|
325
427
|
<!-- A board read that failed for a fixable reason (unreachable site, expired
|
|
326
428
|
token): named, so the user isn't left with an empty picker and no cause. -->
|
|
327
|
-
<p v-if="boardsFailure" class="mt-1 text-xs text-amber-400">
|
|
429
|
+
<p v-else-if="boardsFailure" class="mt-1 text-xs text-amber-400">
|
|
328
430
|
{{ t('bugHunt.boardsFailed', { reason: boardsFailure }) }}
|
|
329
431
|
</p>
|
|
330
432
|
</UFormField>
|
|
@@ -347,16 +449,32 @@ const STATUS_KEYS: Record<BugHuntAnalysisStatus, string> = {
|
|
|
347
449
|
</UFormField>
|
|
348
450
|
</div>
|
|
349
451
|
|
|
350
|
-
<!-- Where an adopted bug lands
|
|
351
|
-
|
|
352
|
-
|
|
452
|
+
<!-- Where an adopted bug lands, and on a repo-backed tracker WHICH REPOSITORY is
|
|
453
|
+
scanned, so the wording says both rather than leaving the scope unexplained. Stated
|
|
454
|
+
when the frame this hunt was opened from is the only legal target; a choice (scoped
|
|
455
|
+
to that frame) when it has modules, or over the whole board when the hunt was opened
|
|
456
|
+
standalone. -->
|
|
457
|
+
<!-- Two blocks rather than one with a computed `keypath`: the i18n extractor reads a
|
|
458
|
+
bound keypath as the key itself, so a dynamic one is a key missing from every
|
|
459
|
+
locale. Every other `<i18n-t>` in the SPA names its key statically for that reason. -->
|
|
353
460
|
<p v-if="containerStated" class="text-xs text-slate-400">
|
|
354
|
-
<i18n-t keypath="bugHunt.
|
|
461
|
+
<i18n-t v-if="boardFromService" keypath="bugHunt.huntingIn" tag="span" scope="global">
|
|
462
|
+
<template #container>
|
|
463
|
+
<span class="font-medium text-slate-200">{{ pinnedContainer!.title }}</span>
|
|
464
|
+
</template>
|
|
465
|
+
</i18n-t>
|
|
466
|
+
<i18n-t v-else keypath="bugHunt.adoptingInto" tag="span" scope="global">
|
|
355
467
|
<template #container>
|
|
356
468
|
<span class="font-medium text-slate-200">{{ pinnedContainer!.title }}</span>
|
|
357
469
|
</template>
|
|
358
470
|
</i18n-t>
|
|
359
471
|
</p>
|
|
472
|
+
<!-- Two fields rather than one with a computed key, for the reason the two blocks above
|
|
473
|
+
are two: the i18n extractor reads a bound key as the key itself, so a dynamic one
|
|
474
|
+
leaves BOTH real keys unreferenced and a dead-key sweep prunes them. -->
|
|
475
|
+
<UFormField v-else-if="boardFromService" :label="t('bugHunt.huntIn')">
|
|
476
|
+
<USelect v-model="containerId" :items="containerItems" class="w-full" />
|
|
477
|
+
</UFormField>
|
|
360
478
|
<UFormField v-else :label="t('bugHunt.adoptInto')">
|
|
361
479
|
<USelect v-model="containerId" :items="containerItems" class="w-full" />
|
|
362
480
|
</UFormField>
|
|
@@ -379,6 +497,12 @@ const STATUS_KEYS: Record<BugHuntAnalysisStatus, string> = {
|
|
|
379
497
|
<!-- Results -->
|
|
380
498
|
<div v-if="hunt.hasResult" class="space-y-3 border-t border-slate-800 pt-3">
|
|
381
499
|
<p class="text-xs text-slate-400">
|
|
500
|
+
<!-- The board the scan actually ran against, named because on a repo-backed tracker
|
|
501
|
+
the platform resolved it: the person reading the shortlist should not have to
|
|
502
|
+
infer which repository it came out of. -->
|
|
503
|
+
<span class="text-slate-500">
|
|
504
|
+
{{ t('bugHunt.scannedBoard', { board: hunt.result!.board }) }}
|
|
505
|
+
</span>
|
|
382
506
|
{{ t(STATUS_KEYS[hunt.result!.analysisStatus]) }}
|
|
383
507
|
<span v-if="hunt.result!.model" class="text-slate-500">
|
|
384
508
|
{{ t('bugHunt.viaModel', { model: hunt.result!.model }) }}
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
|
2
2
|
import { useBugHuntStore } from '~/stores/bugHunt'
|
|
3
3
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
4
4
|
import { ApiError } from '~/composables/api/errors'
|
|
5
|
-
import type { TaskSourceKind, TrackerBoardsView } from '~/types/domain'
|
|
5
|
+
import type { BugHuntResult, TaskSourceKind, TrackerBoardsView } from '~/types/domain'
|
|
6
6
|
|
|
7
7
|
// What the board picker does with a FAILED board read. Only one failure means "this tracker
|
|
8
8
|
// cannot enumerate boards, so type one in"; every other failure has to stay visible as an error,
|
|
@@ -23,20 +23,43 @@ const boardsUnsupported = () =>
|
|
|
23
23
|
function stubApi(): {
|
|
24
24
|
store: ReturnType<typeof useBugHuntStore>
|
|
25
25
|
serve: (fn: () => Promise<unknown>) => void
|
|
26
|
+
serveHunt: (fn: () => Promise<unknown>) => void
|
|
26
27
|
} {
|
|
27
28
|
let handler: () => Promise<unknown> = () => Promise.resolve({ source: 'jira', boards: [] })
|
|
29
|
+
let huntHandler: () => Promise<unknown> = () => Promise.resolve(huntResult())
|
|
28
30
|
vi.stubGlobal('useApi', () => ({
|
|
29
31
|
listTrackerBoards: (_ws: string, _source: TaskSourceKind) =>
|
|
30
32
|
handler() as Promise<TrackerBoardsView>,
|
|
33
|
+
runBugHunt: (_ws: string, _source: TaskSourceKind, _input: unknown) =>
|
|
34
|
+
huntHandler() as Promise<BugHuntResult>,
|
|
31
35
|
}))
|
|
32
36
|
return {
|
|
33
37
|
store: useBugHuntStore(),
|
|
34
38
|
serve: (fn) => {
|
|
35
39
|
handler = fn
|
|
36
40
|
},
|
|
41
|
+
serveHunt: (fn) => {
|
|
42
|
+
huntHandler = fn
|
|
43
|
+
},
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
/** An empty but well-formed scan result, so a success case asserts on the store, not the shape. */
|
|
48
|
+
function huntResult(): BugHuntResult {
|
|
49
|
+
return {
|
|
50
|
+
source: 'github',
|
|
51
|
+
board: 'acme/web',
|
|
52
|
+
analysisStatus: 'empty',
|
|
53
|
+
model: null,
|
|
54
|
+
candidates: [],
|
|
55
|
+
scanned: 0,
|
|
56
|
+
truncated: false,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The one scan input shape the modal builds; the store only passes it through. */
|
|
61
|
+
const SCAN = { containerId: 'blk_auth', board: null }
|
|
62
|
+
|
|
40
63
|
describe('bug hunt store — board listing failures', () => {
|
|
41
64
|
beforeEach(() => {
|
|
42
65
|
useWorkspaceStore().workspaceId = 'ws1'
|
|
@@ -79,6 +102,22 @@ describe('bug hunt store — board listing failures', () => {
|
|
|
79
102
|
expect(store.boards.map((b) => b.id)).toEqual(['t1'])
|
|
80
103
|
})
|
|
81
104
|
|
|
105
|
+
it('drops the previous tracker failure when the next one has no board to list', async () => {
|
|
106
|
+
// A repo-backed tracker renders no board field at all, so a stale "boards could not be
|
|
107
|
+
// loaded" warning would sit under a control that is not there, blaming this tracker for the
|
|
108
|
+
// last one's outage.
|
|
109
|
+
const { store, serve } = stubApi()
|
|
110
|
+
serve(() => Promise.reject(apiError(502, 'upstream')))
|
|
111
|
+
await store.loadBoards('jira')
|
|
112
|
+
|
|
113
|
+
store.dropBoards('github')
|
|
114
|
+
|
|
115
|
+
expect(store.boardsSource).toBe('github')
|
|
116
|
+
expect(store.boards).toEqual([])
|
|
117
|
+
expect(store.boardsError).toBeNull()
|
|
118
|
+
expect(store.boardsErrorReason).toBeNull()
|
|
119
|
+
})
|
|
120
|
+
|
|
82
121
|
it('a source switch mid-flight never lands the older tracker failure on the newer one', async () => {
|
|
83
122
|
const { store, serve } = stubApi()
|
|
84
123
|
let rejectJira!: (e: unknown) => void
|
|
@@ -100,4 +139,86 @@ describe('bug hunt store — board listing failures', () => {
|
|
|
100
139
|
expect(store.boardsErrorReason).toBeNull()
|
|
101
140
|
expect(store.boardsError).toBeNull()
|
|
102
141
|
})
|
|
142
|
+
|
|
143
|
+
it('leaves nothing loading when the next tracker has no board to list', async () => {
|
|
144
|
+
// The abandoned listing's own `finally` will not run until it settles, which for a hanging
|
|
145
|
+
// tracker is never. A picker (or a Hunt button) gated on the flag would wait on a request
|
|
146
|
+
// nobody is waiting for.
|
|
147
|
+
const { store, serve } = stubApi()
|
|
148
|
+
serve(() => new Promise(() => {}))
|
|
149
|
+
void store.loadBoards('jira')
|
|
150
|
+
expect(store.boardsLoading).toBe(true)
|
|
151
|
+
|
|
152
|
+
store.dropBoards('github')
|
|
153
|
+
|
|
154
|
+
expect(store.boardsLoading).toBe(false)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('a superseded listing never reports the tracker now loading as done', async () => {
|
|
158
|
+
const { store, serve } = stubApi()
|
|
159
|
+
let settleJira!: (v: unknown) => void
|
|
160
|
+
serve(() => new Promise((res) => (settleJira = res)))
|
|
161
|
+
const inFlight = store.loadBoards('jira')
|
|
162
|
+
|
|
163
|
+
serve(() => new Promise(() => {}))
|
|
164
|
+
void store.loadBoards('linear')
|
|
165
|
+
settleJira({ source: 'jira', boards: [] })
|
|
166
|
+
await inFlight
|
|
167
|
+
|
|
168
|
+
expect(store.boardsSource).toBe('linear')
|
|
169
|
+
expect(store.boardsLoading).toBe(true)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe('bug hunt store — scan failures', () => {
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
useWorkspaceStore().workspaceId = 'ws1'
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('keeps the backend reason for the one failure the surface words itself', async () => {
|
|
179
|
+
// `repo_not_linked` names something fixable on this board, so the modal states it beside the
|
|
180
|
+
// scope it invalidates instead of raising a toast. That routing reads ONLY this field.
|
|
181
|
+
const { store, serveHunt } = stubApi()
|
|
182
|
+
serveHunt(() => Promise.reject(apiError(422, 'validation', { reason: 'repo_not_linked' })))
|
|
183
|
+
|
|
184
|
+
expect(await store.hunt('github', SCAN)).toBe(false)
|
|
185
|
+
|
|
186
|
+
expect(store.huntErrorReason).toBe('repo_not_linked')
|
|
187
|
+
expect(store.huntError).toBeTruthy()
|
|
188
|
+
expect(store.result).toBeNull()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('records NO reason for a scan that simply failed, so it stays a toast', async () => {
|
|
192
|
+
const { store, serveHunt } = stubApi()
|
|
193
|
+
serveHunt(() => Promise.reject(apiError(502, 'upstream')))
|
|
194
|
+
|
|
195
|
+
expect(await store.hunt('jira', { containerId: 'blk_auth', board: 'PROJ' })).toBe(false)
|
|
196
|
+
|
|
197
|
+
expect(store.huntErrorReason).toBeNull()
|
|
198
|
+
expect(store.huntError).toBeTruthy()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('clears a previous scan failure once a later scan succeeds', async () => {
|
|
202
|
+
const { store, serveHunt } = stubApi()
|
|
203
|
+
serveHunt(() => Promise.reject(apiError(422, 'validation', { reason: 'repo_not_linked' })))
|
|
204
|
+
await store.hunt('github', SCAN)
|
|
205
|
+
|
|
206
|
+
serveHunt(() => Promise.resolve(huntResult()))
|
|
207
|
+
expect(await store.hunt('github', SCAN)).toBe(true)
|
|
208
|
+
|
|
209
|
+
expect(store.huntError).toBeNull()
|
|
210
|
+
expect(store.huntErrorReason).toBeNull()
|
|
211
|
+
expect(store.result?.board).toBe('acme/web')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('drops the reason on reset, so a re-opened hunt never re-states the old refusal', async () => {
|
|
215
|
+
const { store, serveHunt } = stubApi()
|
|
216
|
+
serveHunt(() => Promise.reject(apiError(422, 'validation', { reason: 'repo_not_linked' })))
|
|
217
|
+
await store.hunt('github', SCAN)
|
|
218
|
+
|
|
219
|
+
store.reset()
|
|
220
|
+
|
|
221
|
+
expect(store.huntErrorReason).toBeNull()
|
|
222
|
+
expect(store.huntError).toBeNull()
|
|
223
|
+
})
|
|
103
224
|
})
|
package/app/stores/bugHunt.ts
CHANGED
|
@@ -38,6 +38,13 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
|
|
|
38
38
|
const result = ref<BugHuntResult | null>(null)
|
|
39
39
|
const hunting = ref(false)
|
|
40
40
|
const huntError = ref<string | null>(null)
|
|
41
|
+
/**
|
|
42
|
+
* The backend's machine-readable reason for a failed scan, kept for the same reason
|
|
43
|
+
* {@link boardsErrorReason} is: `repo_not_linked` says the service this hunt is scoped to has
|
|
44
|
+
* no repository to read issues from, which is a state the person can fix on the board and the
|
|
45
|
+
* only one the surface words itself. Every other failure stays a toast.
|
|
46
|
+
*/
|
|
47
|
+
const huntErrorReason = ref<string | null>(null)
|
|
41
48
|
/** The candidate currently being adopted, so only its own row shows a spinner. */
|
|
42
49
|
const adopting = ref<string | null>(null)
|
|
43
50
|
|
|
@@ -62,20 +69,44 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
|
|
|
62
69
|
boardsError.value = e instanceof Error ? e.message : String(e)
|
|
63
70
|
boardsErrorReason.value = apiErrorReason(e)
|
|
64
71
|
} finally {
|
|
65
|
-
|
|
72
|
+
// Only the listing the picker is still showing owns the flag. A superseded one clearing it
|
|
73
|
+
// unconditionally would report the tracker now being loaded as done, which is the same
|
|
74
|
+
// mistake in the opposite direction from the one `dropBoards` avoids.
|
|
75
|
+
if (boardsSource.value === source) boardsLoading.value = false
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Forget the board listing, because the tracker now in the picker has none to offer: its board
|
|
81
|
+
* is the chosen service's repository, resolved server-side. Called INSTEAD of `loadBoards`, so a
|
|
82
|
+
* previous tracker's list (or its failure, which the surface renders as a warning) cannot sit
|
|
83
|
+
* under a tracker that has no board field at all. `boardsSource` moves with it, so a listing
|
|
84
|
+
* still in flight for that previous tracker lands on nothing rather than reviving the picker.
|
|
85
|
+
*/
|
|
86
|
+
function dropBoards(source: TaskSourceKind): void {
|
|
87
|
+
boardsSource.value = source
|
|
88
|
+
boards.value = []
|
|
89
|
+
boardsError.value = null
|
|
90
|
+
boardsErrorReason.value = null
|
|
91
|
+
// The listing in flight belongs to the tracker being left, and its `finally` will not run
|
|
92
|
+
// until it settles — indefinitely, if that tracker hangs. Clearing the flag with the rest of
|
|
93
|
+
// the state is what makes "land on nothing" true of the WHOLE listing rather than of four of
|
|
94
|
+
// its five fields, so a reader gating on it cannot wait on a request nobody is waiting for.
|
|
95
|
+
boardsLoading.value = false
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
/** Run a hunt and keep its ranked result. Returns false when the scan itself failed. */
|
|
70
99
|
async function hunt(source: TaskSourceKind, input: RunBugHuntInput): Promise<boolean> {
|
|
71
100
|
hunting.value = true
|
|
72
101
|
huntError.value = null
|
|
102
|
+
huntErrorReason.value = null
|
|
73
103
|
try {
|
|
74
104
|
result.value = await api.runBugHunt(workspace.requireId(), source, input)
|
|
75
105
|
return true
|
|
76
106
|
} catch (e) {
|
|
77
107
|
result.value = null
|
|
78
108
|
huntError.value = e instanceof Error ? e.message : String(e)
|
|
109
|
+
huntErrorReason.value = apiErrorReason(e)
|
|
79
110
|
return false
|
|
80
111
|
} finally {
|
|
81
112
|
hunting.value = false
|
|
@@ -117,6 +148,7 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
|
|
|
117
148
|
function reset(): void {
|
|
118
149
|
result.value = null
|
|
119
150
|
huntError.value = null
|
|
151
|
+
huntErrorReason.value = null
|
|
120
152
|
adopting.value = null
|
|
121
153
|
}
|
|
122
154
|
|
|
@@ -131,8 +163,10 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
|
|
|
131
163
|
hasResult,
|
|
132
164
|
hunting,
|
|
133
165
|
huntError,
|
|
166
|
+
huntErrorReason,
|
|
134
167
|
adopting,
|
|
135
168
|
loadBoards,
|
|
169
|
+
dropBoards,
|
|
136
170
|
hunt,
|
|
137
171
|
adopt,
|
|
138
172
|
reset,
|
package/app/stores/tasks.spec.ts
CHANGED
|
@@ -34,6 +34,8 @@ const jiraDescriptor: TaskSourceState = {
|
|
|
34
34
|
enabled: true,
|
|
35
35
|
supportsIntake: true,
|
|
36
36
|
ignoredIntakePredicates: [],
|
|
37
|
+
// Jira issues belong to a project, not a repository, so a hunt on it picks a board.
|
|
38
|
+
repoBacked: false,
|
|
37
39
|
// Jira carries its own credentials, so it rides no VCS connection.
|
|
38
40
|
ridesVcsProvider: null,
|
|
39
41
|
}
|
package/i18n/locales/de.json
CHANGED
|
@@ -4296,7 +4296,9 @@
|
|
|
4296
4296
|
"tracker": "Tracker",
|
|
4297
4297
|
"board": "Board",
|
|
4298
4298
|
"pickBoard": "Board auswählen",
|
|
4299
|
-
"boardPlaceholder": "Projektschlüssel
|
|
4299
|
+
"boardPlaceholder": "Projektschlüssel oder Team-ID",
|
|
4300
|
+
"boardFromService": "Das mit diesem Service verknüpfte Repository",
|
|
4301
|
+
"boardNeedsRepo": "Mit diesem Service ist kein Repository verknüpft, es gibt also keine Tickets zu durchsuchen. Verknüpfe eines im Service-Panel.",
|
|
4300
4302
|
"boardsFailed": "Boards konnten nicht geladen werden: {reason}",
|
|
4301
4303
|
"issueType": "Vorgangstyp",
|
|
4302
4304
|
"issueTypeHelp": "Standard ist bug. Wird von Trackern ohne Vorgangstypen ignoriert.",
|
|
@@ -4305,6 +4307,8 @@
|
|
|
4305
4307
|
"labelsHelp": "Durch Komma getrennt. Alle müssen vorhanden sein.",
|
|
4306
4308
|
"adoptInto": "Ausgewählten Fehler hinzufügen zu",
|
|
4307
4309
|
"adoptingInto": "Der ausgewählte Fehler landet in {container}",
|
|
4310
|
+
"huntIn": "Zu durchsuchender Service",
|
|
4311
|
+
"huntingIn": "Durchsucht wird das mit {container} verknüpfte Repository; dort landet auch der ausgewählte Fehler.",
|
|
4308
4312
|
"run": "Jagen",
|
|
4309
4313
|
"running": "Board wird gelesen und die Funde werden bewertet…",
|
|
4310
4314
|
"huntFailed": "Die Jagd ist fehlgeschlagen",
|
|
@@ -4317,6 +4321,7 @@
|
|
|
4317
4321
|
"noCandidates": "Auf diesem Board gab es keine offenen, nicht zugewiesenen Fehler.",
|
|
4318
4322
|
"ratings": "Auswirkung {impact}/5, Aufwand {complexity}/5, Konfidenz {confidence}",
|
|
4319
4323
|
"viaModel": "Bewertet von {model}.",
|
|
4324
|
+
"scannedBoard": "{board} durchsucht.",
|
|
4320
4325
|
"truncated": "Es wurden nur die ersten {count} passenden Fehler geprüft; dieses Board enthält mehr.",
|
|
4321
4326
|
"comments": "{count} Kommentar | {count} Kommentare",
|
|
4322
4327
|
"confidence": {
|
|
@@ -4330,6 +4335,10 @@
|
|
|
4330
4335
|
"failed": "Die Bewertung konnte nicht abgeschlossen werden, daher sind diese unbewertet.",
|
|
4331
4336
|
"over_budget": "Dieser Arbeitsbereich hat sein Ausgabenbudget überschritten, daher wurden diese nicht bewertet.",
|
|
4332
4337
|
"empty": "Nichts zu bewerten."
|
|
4338
|
+
},
|
|
4339
|
+
"refusal": {
|
|
4340
|
+
"boardFromService": "Dieser Tracker durchsucht das Repository, mit dem der gewählte Service verknüpft ist, und hat daher kein eigenes Board.",
|
|
4341
|
+
"missingBoard": "Wähle das Board aus, auf dem gesucht werden soll."
|
|
4333
4342
|
}
|
|
4334
4343
|
},
|
|
4335
4344
|
"pipeline": {
|
package/i18n/locales/en.json
CHANGED
|
@@ -4897,7 +4897,9 @@
|
|
|
4897
4897
|
"tracker": "Tracker",
|
|
4898
4898
|
"board": "Board",
|
|
4899
4899
|
"pickBoard": "Pick a board",
|
|
4900
|
-
"boardPlaceholder": "Project key
|
|
4900
|
+
"boardPlaceholder": "Project key or team id",
|
|
4901
|
+
"boardFromService": "The repository this service is linked to",
|
|
4902
|
+
"boardNeedsRepo": "This service has no repository linked, so it has no issues to hunt. Link one from the service panel.",
|
|
4901
4903
|
"boardsFailed": "Boards could not be loaded: {reason}",
|
|
4902
4904
|
"issueType": "Issue type",
|
|
4903
4905
|
"issueTypeHelp": "Defaults to bug. Ignored by trackers with no issue types.",
|
|
@@ -4906,6 +4908,8 @@
|
|
|
4906
4908
|
"labelsHelp": "Comma separated. All of them must be present.",
|
|
4907
4909
|
"adoptInto": "Add the picked bug to",
|
|
4908
4910
|
"adoptingInto": "The picked bug lands in {container}",
|
|
4911
|
+
"huntIn": "Service to hunt",
|
|
4912
|
+
"huntingIn": "Hunting the repository linked to {container}; the picked bug lands there too.",
|
|
4909
4913
|
"run": "Hunt",
|
|
4910
4914
|
"running": "Reading the board and rating what it finds…",
|
|
4911
4915
|
"huntFailed": "The hunt failed",
|
|
@@ -4918,6 +4922,7 @@
|
|
|
4918
4922
|
"noCandidates": "No open, unassigned bugs matched on this board.",
|
|
4919
4923
|
"ratings": "Impact {impact}/5, complexity {complexity}/5, {confidence} confidence",
|
|
4920
4924
|
"viaModel": "Rated by {model}.",
|
|
4925
|
+
"scannedBoard": "Scanned {board}.",
|
|
4921
4926
|
"truncated": "Only the first {count} matching bugs were scanned; this board holds more.",
|
|
4922
4927
|
"comments": "{count} comment | {count} comments",
|
|
4923
4928
|
"@comments": {
|
|
@@ -4934,6 +4939,10 @@
|
|
|
4934
4939
|
"failed": "The rating could not be completed, so these are unrated.",
|
|
4935
4940
|
"over_budget": "This workspace is over its spend budget, so these were not rated.",
|
|
4936
4941
|
"empty": "Nothing to rate."
|
|
4942
|
+
},
|
|
4943
|
+
"refusal": {
|
|
4944
|
+
"boardFromService": "This tracker scans the repository the chosen service is linked to, so it has no board of its own.",
|
|
4945
|
+
"missingBoard": "Pick the board to hunt on."
|
|
4937
4946
|
}
|
|
4938
4947
|
},
|
|
4939
4948
|
"pipeline": {
|
package/i18n/locales/es.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "Gestor de incidencias",
|
|
4740
4740
|
"board": "Tablero",
|
|
4741
4741
|
"pickBoard": "Elige un tablero",
|
|
4742
|
-
"boardPlaceholder": "Clave de proyecto
|
|
4742
|
+
"boardPlaceholder": "Clave de proyecto o id de equipo",
|
|
4743
|
+
"boardFromService": "El repositorio vinculado a este servicio",
|
|
4744
|
+
"boardNeedsRepo": "Este servicio no tiene ningún repositorio vinculado, así que no hay incidencias que explorar. Vincula uno desde el panel del servicio.",
|
|
4743
4745
|
"boardsFailed": "No se pudieron cargar los tableros: {reason}",
|
|
4744
4746
|
"issueType": "Tipo de incidencia",
|
|
4745
4747
|
"issueTypeHelp": "Por defecto bug. Se ignora en gestores sin tipos de incidencia.",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "Separadas por comas. Todas deben estar presentes.",
|
|
4749
4751
|
"adoptInto": "Añadir el error elegido a",
|
|
4750
4752
|
"adoptingInto": "El error elegido se añade a {container}",
|
|
4753
|
+
"huntIn": "Servicio a explorar",
|
|
4754
|
+
"huntingIn": "Se explora el repositorio vinculado a {container}; el error elegido también se añade ahí.",
|
|
4751
4755
|
"run": "Cazar",
|
|
4752
4756
|
"running": "Leyendo el tablero y valorando lo que encuentra…",
|
|
4753
4757
|
"huntFailed": "La caza ha fallado",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "No hay errores abiertos y sin asignar que coincidan en este tablero.",
|
|
4761
4765
|
"ratings": "Impacto {impact}/5, complejidad {complexity}/5, confianza {confidence}",
|
|
4762
4766
|
"viaModel": "Valorado por {model}.",
|
|
4767
|
+
"scannedBoard": "Explorado {board}.",
|
|
4763
4768
|
"truncated": "Solo se han explorado los primeros {count} errores coincidentes; este tablero contiene más.",
|
|
4764
4769
|
"comments": "{count} comentario | {count} comentarios",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "No se ha podido completar la valoración, así que están sin valorar.",
|
|
4774
4779
|
"over_budget": "Este espacio de trabajo ha superado su presupuesto de gasto, así que no se han valorado.",
|
|
4775
4780
|
"empty": "Nada que valorar."
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "Este rastreador analiza el repositorio al que está vinculado el servicio elegido, así que no tiene un tablero propio.",
|
|
4784
|
+
"missingBoard": "Elige el tablero en el que buscar."
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/i18n/locales/fr.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "Gestionnaire de tickets",
|
|
4740
4740
|
"board": "Tableau",
|
|
4741
4741
|
"pickBoard": "Choisir un tableau",
|
|
4742
|
-
"boardPlaceholder": "Clé de projet
|
|
4742
|
+
"boardPlaceholder": "Clé de projet ou id d'équipe",
|
|
4743
|
+
"boardFromService": "Le dépôt lié à ce service",
|
|
4744
|
+
"boardNeedsRepo": "Aucun dépôt n'est lié à ce service : il n'y a donc pas de tickets à explorer. Liez-en un depuis le panneau du service.",
|
|
4743
4745
|
"boardsFailed": "Impossible de charger les tableaux : {reason}",
|
|
4744
4746
|
"issueType": "Type de ticket",
|
|
4745
4747
|
"issueTypeHelp": "Par défaut bug. Ignoré par les gestionnaires sans types de tickets.",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "Séparées par des virgules. Toutes doivent être présentes.",
|
|
4749
4751
|
"adoptInto": "Ajouter le bug retenu à",
|
|
4750
4752
|
"adoptingInto": "Le bug retenu est ajouté à {container}",
|
|
4753
|
+
"huntIn": "Service à explorer",
|
|
4754
|
+
"huntingIn": "Exploration du dépôt lié à {container} ; le bug retenu y est ajouté.",
|
|
4751
4755
|
"run": "Chasser",
|
|
4752
4756
|
"running": "Lecture du tableau et évaluation des résultats…",
|
|
4753
4757
|
"huntFailed": "La chasse a échoué",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "Aucun bug ouvert et non assigné ne correspond sur ce tableau.",
|
|
4761
4765
|
"ratings": "Impact {impact}/5, complexité {complexity}/5, confiance {confidence}",
|
|
4762
4766
|
"viaModel": "Évalué par {model}.",
|
|
4767
|
+
"scannedBoard": "{board} exploré.",
|
|
4763
4768
|
"truncated": "Seuls les {count} premiers bugs correspondants ont été parcourus ; ce tableau en contient davantage.",
|
|
4764
4769
|
"comments": "{count} commentaire | {count} commentaires",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "L'évaluation n'a pas pu aboutir, ils sont donc non évalués.",
|
|
4774
4779
|
"over_budget": "Cet espace de travail a dépassé son budget de dépenses, ces bogues n'ont donc pas été évalués.",
|
|
4775
4780
|
"empty": "Rien à évaluer."
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "Ce traqueur analyse le dépôt auquel le service choisi est lié : il n'a donc pas de tableau propre.",
|
|
4784
|
+
"missingBoard": "Choisissez le tableau sur lequel chercher."
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/i18n/locales/he.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "מערכת מעקב",
|
|
4740
4740
|
"board": "לוח",
|
|
4741
4741
|
"pickBoard": "בחר לוח",
|
|
4742
|
-
"boardPlaceholder": "מפתח
|
|
4742
|
+
"boardPlaceholder": "מפתח פרויקט או מזהה צוות",
|
|
4743
|
+
"boardFromService": "המאגר המקושר לשירות הזה",
|
|
4744
|
+
"boardNeedsRepo": "לשירות הזה לא מקושר מאגר, ולכן אין בו תקלות לסרוק. קשר מאגר מתוך פאנל השירות.",
|
|
4743
4745
|
"boardsFailed": "לא ניתן היה לטעון את הלוחות: {reason}",
|
|
4744
4746
|
"issueType": "סוג הפנייה",
|
|
4745
4747
|
"issueTypeHelp": "ברירת המחדל היא bug. מתעלמים ממנו במערכות ללא סוגי פניות.",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "מופרדות בפסיקים. כולן חייבות להופיע.",
|
|
4749
4751
|
"adoptInto": "הוסף את הבאג הנבחר אל",
|
|
4750
4752
|
"adoptingInto": "הבאג הנבחר יתווסף ל-{container}",
|
|
4753
|
+
"huntIn": "השירות לסריקה",
|
|
4754
|
+
"huntingIn": "נסרק המאגר המקושר ל-{container}; הבאג הנבחר יתווסף לשם.",
|
|
4751
4755
|
"run": "צוד",
|
|
4752
4756
|
"running": "קורא את הלוח ומעריך את מה שנמצא…",
|
|
4753
4757
|
"huntFailed": "הציד נכשל",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "אין בלוח הזה באגים פתוחים ולא משויכים שתואמים.",
|
|
4761
4765
|
"ratings": "השפעה {impact}/5, מורכבות {complexity}/5, רמת ודאות {confidence}",
|
|
4762
4766
|
"viaModel": "הוערך על ידי {model}.",
|
|
4767
|
+
"scannedBoard": "נסרק {board}.",
|
|
4763
4768
|
"truncated": "נסרקו רק {count} הבאגים התואמים הראשונים; בלוח הזה יש עוד.",
|
|
4764
4769
|
"comments": "תגובה אחת | שתי תגובות | {count} תגובות",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "לא ניתן היה להשלים את ההערכה, ולכן אלה אינם מדורגים.",
|
|
4774
4779
|
"over_budget": "סביבת העבודה חרגה מתקציב ההוצאות, ולכן הבאגים האלה לא דורגו.",
|
|
4775
4780
|
"empty": "אין מה להעריך."
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "הגששן סורק את המאגר שאליו מקושר השירות שנבחר, ולכן אין לו לוח משלו.",
|
|
4784
|
+
"missingBoard": "בחרו את הלוח שבו לחפש."
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/i18n/locales/it.json
CHANGED
|
@@ -4296,7 +4296,9 @@
|
|
|
4296
4296
|
"tracker": "Tracker",
|
|
4297
4297
|
"board": "Bacheca",
|
|
4298
4298
|
"pickBoard": "Scegli una bacheca",
|
|
4299
|
-
"boardPlaceholder": "Chiave progetto
|
|
4299
|
+
"boardPlaceholder": "Chiave progetto o id team",
|
|
4300
|
+
"boardFromService": "Il repository collegato a questo servizio",
|
|
4301
|
+
"boardNeedsRepo": "A questo servizio non è collegato alcun repository, quindi non ci sono issue da cercare. Collegane uno dal pannello del servizio.",
|
|
4300
4302
|
"boardsFailed": "Impossibile caricare le board: {reason}",
|
|
4301
4303
|
"issueType": "Tipo di ticket",
|
|
4302
4304
|
"issueTypeHelp": "Per impostazione predefinita bug. Ignorato dai tracker senza tipi di ticket.",
|
|
@@ -4305,6 +4307,8 @@
|
|
|
4305
4307
|
"labelsHelp": "Separate da virgole. Devono essere tutte presenti.",
|
|
4306
4308
|
"adoptInto": "Aggiungi il bug scelto a",
|
|
4307
4309
|
"adoptingInto": "Il bug scelto viene aggiunto a {container}",
|
|
4310
|
+
"huntIn": "Servizio da esaminare",
|
|
4311
|
+
"huntingIn": "Viene esaminato il repository collegato a {container}; il bug scelto viene aggiunto lì.",
|
|
4308
4312
|
"run": "Cerca",
|
|
4309
4313
|
"running": "Lettura della bacheca e valutazione di quanto trovato…",
|
|
4310
4314
|
"huntFailed": "La caccia non è riuscita",
|
|
@@ -4317,6 +4321,7 @@
|
|
|
4317
4321
|
"noCandidates": "Nessun bug aperto e non assegnato corrisponde su questa bacheca.",
|
|
4318
4322
|
"ratings": "Impatto {impact}/5, complessità {complexity}/5, confidenza {confidence}",
|
|
4319
4323
|
"viaModel": "Valutato da {model}.",
|
|
4324
|
+
"scannedBoard": "Esaminato {board}.",
|
|
4320
4325
|
"truncated": "Sono stati esaminati solo i primi {count} bug corrispondenti; questa bacheca ne contiene altri.",
|
|
4321
4326
|
"comments": "{count} commento | {count} commenti",
|
|
4322
4327
|
"confidence": {
|
|
@@ -4330,6 +4335,10 @@
|
|
|
4330
4335
|
"failed": "La valutazione non è stata completata, quindi non sono valutati.",
|
|
4331
4336
|
"over_budget": "Questo spazio di lavoro ha superato il budget di spesa, quindi non sono stati valutati.",
|
|
4332
4337
|
"empty": "Nulla da valutare."
|
|
4338
|
+
},
|
|
4339
|
+
"refusal": {
|
|
4340
|
+
"boardFromService": "Questo tracker analizza il repository a cui è collegato il servizio scelto, quindi non ha una board propria.",
|
|
4341
|
+
"missingBoard": "Scegli la board su cui cercare."
|
|
4333
4342
|
}
|
|
4334
4343
|
},
|
|
4335
4344
|
"pipeline": {
|
package/i18n/locales/ja.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "トラッカー",
|
|
4740
4740
|
"board": "ボード",
|
|
4741
4741
|
"pickBoard": "ボードを選択",
|
|
4742
|
-
"boardPlaceholder": "
|
|
4742
|
+
"boardPlaceholder": "プロジェクトキーまたはチーム ID",
|
|
4743
|
+
"boardFromService": "このサービスにリンクされたリポジトリ",
|
|
4744
|
+
"boardNeedsRepo": "このサービスにはリポジトリがリンクされていないため、探索できる課題がありません。サービスパネルからリンクしてください。",
|
|
4743
4745
|
"boardsFailed": "ボードを読み込めませんでした: {reason}",
|
|
4744
4746
|
"issueType": "課題タイプ",
|
|
4745
4747
|
"issueTypeHelp": "既定は bug です。課題タイプを持たないトラッカーでは無視されます。",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "カンマ区切り。すべて付いている必要があります。",
|
|
4749
4751
|
"adoptInto": "選んだバグの追加先",
|
|
4750
4752
|
"adoptingInto": "選んだバグは {container} に追加されます",
|
|
4753
|
+
"huntIn": "探索するサービス",
|
|
4754
|
+
"huntingIn": "{container} にリンクされたリポジトリを探索します。選んだバグもそこに追加されます。",
|
|
4751
4755
|
"run": "ハント",
|
|
4752
4756
|
"running": "ボードを読み取り、見つかったものを評価しています…",
|
|
4753
4757
|
"huntFailed": "ハントに失敗しました",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "このボードには条件に合う未割り当ての未解決バグがありません。",
|
|
4761
4765
|
"ratings": "影響度 {impact}/5、複雑さ {complexity}/5、確信度 {confidence}",
|
|
4762
4766
|
"viaModel": "{model} による評価です。",
|
|
4767
|
+
"scannedBoard": "{board} を走査しました。",
|
|
4763
4768
|
"truncated": "一致したバグのうち最初の {count} 件のみを走査しました。このボードにはさらにあります。",
|
|
4764
4769
|
"comments": "コメント {count} 件 | コメント {count} 件",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "評価を完了できなかったため、未評価のまま表示しています。",
|
|
4774
4779
|
"over_budget": "このワークスペースは利用予算を超えているため、評価は行われませんでした。",
|
|
4775
4780
|
"empty": "評価する対象がありません。"
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "このトラッカーは選択したサービスにリンクされたリポジトリを走査するため、独自のボードはありません。",
|
|
4784
|
+
"missingBoard": "調査するボードを選択してください。"
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/i18n/locales/pl.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "System zgłoszeń",
|
|
4740
4740
|
"board": "Tablica",
|
|
4741
4741
|
"pickBoard": "Wybierz tablicę",
|
|
4742
|
-
"boardPlaceholder": "Klucz projektu
|
|
4742
|
+
"boardPlaceholder": "Klucz projektu lub identyfikator zespołu",
|
|
4743
|
+
"boardFromService": "Repozytorium powiązane z tą usługą",
|
|
4744
|
+
"boardNeedsRepo": "Ta usługa nie ma powiązanego repozytorium, więc nie ma w niej zgłoszeń do przeszukania. Powiąż je w panelu usługi.",
|
|
4743
4745
|
"boardsFailed": "Nie udało się wczytać tablic: {reason}",
|
|
4744
4746
|
"issueType": "Typ zgłoszenia",
|
|
4745
4747
|
"issueTypeHelp": "Domyślnie bug. Ignorowane przez systemy bez typów zgłoszeń.",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "Oddzielone przecinkami. Wszystkie muszą występować.",
|
|
4749
4751
|
"adoptInto": "Dodaj wybrany błąd do",
|
|
4750
4752
|
"adoptingInto": "Wybrany błąd trafi do {container}",
|
|
4753
|
+
"huntIn": "Usługa do przeszukania",
|
|
4754
|
+
"huntingIn": "Przeszukiwane jest repozytorium powiązane z {container}; tam też trafi wybrany błąd.",
|
|
4751
4755
|
"run": "Poluj",
|
|
4752
4756
|
"running": "Odczytywanie tablicy i ocenianie znalezionych zgłoszeń…",
|
|
4753
4757
|
"huntFailed": "Polowanie nie powiodło się",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "Na tej tablicy nie ma pasujących otwartych, nieprzypisanych błędów.",
|
|
4761
4765
|
"ratings": "Wpływ {impact}/5, złożoność {complexity}/5, pewność {confidence}",
|
|
4762
4766
|
"viaModel": "Ocenione przez {model}.",
|
|
4767
|
+
"scannedBoard": "Przeszukano {board}.",
|
|
4763
4768
|
"truncated": "Przeszukano tylko pierwsze pasujące zgłoszenia w liczbie {count}; ta tablica zawiera ich więcej.",
|
|
4764
4769
|
"comments": "{count} komentarz | {count} komentarze | {count} komentarzy",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "Nie udało się dokończyć oceny, więc te zgłoszenia są nieocenione.",
|
|
4774
4779
|
"over_budget": "Ta przestrzeń robocza przekroczyła budżet wydatków, więc nie zostały ocenione.",
|
|
4775
4780
|
"empty": "Nie ma czego oceniać."
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "Ten tracker przeszukuje repozytorium powiązane z wybraną usługą, więc nie ma własnej tablicy.",
|
|
4784
|
+
"missingBoard": "Wybierz tablicę, na której chcesz szukać."
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/i18n/locales/tr.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "Takip aracı",
|
|
4740
4740
|
"board": "Pano",
|
|
4741
4741
|
"pickBoard": "Bir pano seçin",
|
|
4742
|
-
"boardPlaceholder": "Proje
|
|
4742
|
+
"boardPlaceholder": "Proje anahtarı veya takım kimliği",
|
|
4743
|
+
"boardFromService": "Bu servise bağlı depo",
|
|
4744
|
+
"boardNeedsRepo": "Bu servise bağlı bir depo yok, dolayısıyla taranacak kayıt da yok. Servis panelinden bir depo bağlayın.",
|
|
4743
4745
|
"boardsFailed": "Panolar yüklenemedi: {reason}",
|
|
4744
4746
|
"issueType": "Kayıt türü",
|
|
4745
4747
|
"issueTypeHelp": "Varsayılan olarak bug. Kayıt türü olmayan araçlarda yok sayılır.",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "Virgülle ayrılır. Hepsinin bulunması gerekir.",
|
|
4749
4751
|
"adoptInto": "Seçilen hatayı şuraya ekle",
|
|
4750
4752
|
"adoptingInto": "Seçilen hata {container} içine eklenir",
|
|
4753
|
+
"huntIn": "Taranacak servis",
|
|
4754
|
+
"huntingIn": "{container} servisine bağlı depo taranıyor; seçilen hata da oraya eklenir.",
|
|
4751
4755
|
"run": "Avla",
|
|
4752
4756
|
"running": "Pano okunuyor ve bulunanlar değerlendiriliyor…",
|
|
4753
4757
|
"huntFailed": "Av başarısız oldu",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "Bu panoda eşleşen açık ve atanmamış hata yok.",
|
|
4761
4765
|
"ratings": "Etki {impact}/5, karmaşıklık {complexity}/5, güven {confidence}",
|
|
4762
4766
|
"viaModel": "{model} tarafından değerlendirildi.",
|
|
4767
|
+
"scannedBoard": "{board} tarandı.",
|
|
4763
4768
|
"truncated": "Yalnızca eşleşen ilk {count} hata tarandı; bu panoda daha fazlası var.",
|
|
4764
4769
|
"comments": "{count} yorum | {count} yorum",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "Değerlendirme tamamlanamadı, bu yüzden bunlar değerlendirilmedi.",
|
|
4774
4779
|
"over_budget": "Bu çalışma alanı harcama bütçesini aştığı için bunlar değerlendirilmedi.",
|
|
4775
4780
|
"empty": "Değerlendirilecek bir şey yok."
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "Bu izleyici, seçilen hizmete bağlı depoyu tarar; bu yüzden kendine ait bir panosu yoktur.",
|
|
4784
|
+
"missingBoard": "Aranacak panoyu seçin."
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/i18n/locales/uk.json
CHANGED
|
@@ -4739,7 +4739,9 @@
|
|
|
4739
4739
|
"tracker": "Трекер",
|
|
4740
4740
|
"board": "Дошка",
|
|
4741
4741
|
"pickBoard": "Оберіть дошку",
|
|
4742
|
-
"boardPlaceholder": "Ключ
|
|
4742
|
+
"boardPlaceholder": "Ключ проєкту або ідентифікатор команди",
|
|
4743
|
+
"boardFromService": "Репозиторій, пов'язаний із цим сервісом",
|
|
4744
|
+
"boardNeedsRepo": "Із цим сервісом не пов'язано жодного репозиторію, тож у ньому немає задач для перегляду. Пов'яжіть репозиторій на панелі сервісу.",
|
|
4743
4745
|
"boardsFailed": "Не вдалося завантажити дошки: {reason}",
|
|
4744
4746
|
"issueType": "Тип запиту",
|
|
4745
4747
|
"issueTypeHelp": "Типово bug. Ігнорується трекерами без типів запитів.",
|
|
@@ -4748,6 +4750,8 @@
|
|
|
4748
4750
|
"labelsHelp": "Через кому. Усі мають бути присутні.",
|
|
4749
4751
|
"adoptInto": "Додати обрану помилку до",
|
|
4750
4752
|
"adoptingInto": "Обрана помилка додається до {container}",
|
|
4753
|
+
"huntIn": "Сервіс для перегляду",
|
|
4754
|
+
"huntingIn": "Переглядається репозиторій, пов'язаний із {container}; туди ж потрапить обрана помилка.",
|
|
4751
4755
|
"run": "Полювати",
|
|
4752
4756
|
"running": "Читаємо дошку та оцінюємо знайдене…",
|
|
4753
4757
|
"huntFailed": "Полювання не вдалося",
|
|
@@ -4760,6 +4764,7 @@
|
|
|
4760
4764
|
"noCandidates": "На цій дошці немає відкритих і не призначених помилок, що відповідають умовам.",
|
|
4761
4765
|
"ratings": "Вплив {impact}/5, складність {complexity}/5, впевненість {confidence}",
|
|
4762
4766
|
"viaModel": "Оцінено моделлю {model}.",
|
|
4767
|
+
"scannedBoard": "Переглянуто {board}.",
|
|
4763
4768
|
"truncated": "Переглянуто лише перші відповідні помилки в кількості {count}; на цій дошці їх більше.",
|
|
4764
4769
|
"comments": "{count} коментар | {count} коментарі | {count} коментарів",
|
|
4765
4770
|
"confidence": {
|
|
@@ -4773,6 +4778,10 @@
|
|
|
4773
4778
|
"failed": "Не вдалося завершити оцінювання, тому ці помилки не оцінено.",
|
|
4774
4779
|
"over_budget": "Цей робочий простір перевищив бюджет витрат, тому їх не оцінено.",
|
|
4775
4780
|
"empty": "Нема чого оцінювати."
|
|
4781
|
+
},
|
|
4782
|
+
"refusal": {
|
|
4783
|
+
"boardFromService": "Цей трекер сканує репозиторій, з яким пов’язано вибраний сервіс, тож власної дошки не має.",
|
|
4784
|
+
"missingBoard": "Виберіть дошку, на якій шукати."
|
|
4776
4785
|
}
|
|
4777
4786
|
},
|
|
4778
4787
|
"pipeline": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.272.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@modular-vue/vue": "^1.4.1",
|
|
27
27
|
"@nuxt/ui": "^4.10.0",
|
|
28
28
|
"@nuxtjs/i18n": "^10.6.0",
|
|
29
|
-
"@pinia/nuxt": "^1.0.
|
|
29
|
+
"@pinia/nuxt": "^1.0.2",
|
|
30
30
|
"@toad-contracts/core": "0.4.0",
|
|
31
31
|
"@toad-contracts/frontend-http-client": "0.3.2",
|
|
32
32
|
"@toad-contracts/valibot": "0.5.0",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"@vue-flow/node-resizer": "^1.5.1",
|
|
36
36
|
"@vueuse/core": "^14.4.0",
|
|
37
37
|
"markdown-it": "^15.0.0",
|
|
38
|
-
"pinia": "^4.0.
|
|
38
|
+
"pinia": "^4.0.3",
|
|
39
39
|
"pinia-plugin-persistedstate": "^4.7.1",
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.41",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.311.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|