Skip to content

Commit cc0b7fb

Browse files
committed
refactor(security-agent): align web with worker orchestration
1 parent 7c2d1b4 commit cc0b7fb

15 files changed

Lines changed: 65 additions & 1272 deletions

apps/web/src/components/security-agent/FindingDetailDialog.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import type { SecurityFinding } from '@kilocode/db/schema';
2929
import { useTRPC } from '@/lib/trpc/utils';
3030
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
3131
import Link from 'next/link';
32+
import { toast } from 'sonner';
33+
import { manualAnalysisAdmissionCopy } from './manual-analysis-admission-copy';
3234

3335
type Severity = 'critical' | 'high' | 'medium' | 'low';
3436

@@ -106,6 +108,7 @@ export function FindingDetailDialog({
106108
const startOrgAnalysisMutation = useMutation(
107109
trpc.organizations.securityAgent.startAnalysis.mutationOptions({
108110
onSuccess: async () => {
111+
toast.success(manualAnalysisAdmissionCopy.successTitle);
109112
await queryClient.invalidateQueries();
110113
},
111114
})
@@ -115,6 +118,7 @@ export function FindingDetailDialog({
115118
const startUserAnalysisMutation = useMutation(
116119
trpc.securityAgent.startAnalysis.mutationOptions({
117120
onSuccess: async () => {
121+
toast.success(manualAnalysisAdmissionCopy.successTitle);
118122
await queryClient.invalidateQueries();
119123
},
120124
})

apps/web/src/components/security-agent/SecurityAgentContext.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { SecurityFinding } from '@kilocode/db/schema';
88
import { isGitHubIntegrationError } from '@/lib/security-agent/core/error-display';
99
import type { DismissReason } from './DismissFindingDialog';
1010
import type { SlaConfig } from './SecurityConfigForm';
11+
import { manualAnalysisAdmissionCopy } from './manual-analysis-admission-copy';
1112

1213
type SecurityAgentContextValue = {
1314
organizationId: string | undefined;
@@ -254,7 +255,7 @@ export function SecurityAgentProvider({ organizationId, children }: SecurityAgen
254255
trpc.organizations.securityAgent.startAnalysis.mutationOptions({
255256
onSuccess: async (_data, variables) => {
256257
setGitHubError(null);
257-
toast.success('Analysis started');
258+
toast.success(manualAnalysisAdmissionCopy.successTitle);
258259
refreshAcceptedQueueMutation();
259260
setStartingAnalysisIds(prev => {
260261
const next = new Set(prev);
@@ -271,7 +272,10 @@ export function SecurityAgentProvider({ organizationId, children }: SecurityAgen
271272
'The GitHub App may have been uninstalled. Please check your integrations.',
272273
});
273274
} else {
274-
toast.error('Failed to start analysis', { description: message, duration: 8000 });
275+
toast.error(manualAnalysisAdmissionCopy.failureTitle, {
276+
description: message,
277+
duration: 8000,
278+
});
275279
}
276280
void queryClient.invalidateQueries();
277281
setStartingAnalysisIds(prev => {
@@ -370,7 +374,7 @@ export function SecurityAgentProvider({ organizationId, children }: SecurityAgen
370374
trpc.securityAgent.startAnalysis.mutationOptions({
371375
onSuccess: async (_data, variables) => {
372376
setGitHubError(null);
373-
toast.success('Analysis started');
377+
toast.success(manualAnalysisAdmissionCopy.successTitle);
374378
refreshAcceptedQueueMutation();
375379
setStartingAnalysisIds(prev => {
376380
const next = new Set(prev);
@@ -387,7 +391,10 @@ export function SecurityAgentProvider({ organizationId, children }: SecurityAgen
387391
'The GitHub App may have been uninstalled. Please check your integrations.',
388392
});
389393
} else {
390-
toast.error('Failed to start analysis', { description: message, duration: 8000 });
394+
toast.error(manualAnalysisAdmissionCopy.failureTitle, {
395+
description: message,
396+
duration: 8000,
397+
});
391398
}
392399
void queryClient.invalidateQueries();
393400
setStartingAnalysisIds(prev => {

apps/web/src/components/security-agent/SecurityAgentPageClient.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '@/lib/security-agent/core/schemas';
2323
import Link from 'next/link';
2424
import { isGitHubIntegrationError } from '@/lib/security-agent/core/error-display';
25+
import { manualAnalysisAdmissionCopy } from './manual-analysis-admission-copy';
2526

2627
type SecurityAgentPageClientProps = {
2728
organizationId?: string;
@@ -332,7 +333,7 @@ export function SecurityAgentPageClient({ organizationId }: SecurityAgentPageCli
332333
trpc.organizations.securityAgent.startAnalysis.mutationOptions({
333334
onSuccess: async (_data, variables) => {
334335
setGitHubError(null); // Clear any previous error on success
335-
toast.success('Analysis started');
336+
toast.success(manualAnalysisAdmissionCopy.successTitle);
336337
refreshAcceptedQueueMutation();
337338
setStartingAnalysisIds(prev => {
338339
const next = new Set(prev);
@@ -349,7 +350,7 @@ export function SecurityAgentPageClient({ organizationId }: SecurityAgentPageCli
349350
'The GitHub App may have been uninstalled. Please check your integrations.',
350351
});
351352
} else {
352-
toast.error('Failed to start analysis', {
353+
toast.error(manualAnalysisAdmissionCopy.failureTitle, {
353354
description: message,
354355
duration: 8000,
355356
});
@@ -371,7 +372,7 @@ export function SecurityAgentPageClient({ organizationId }: SecurityAgentPageCli
371372
trpc.securityAgent.startAnalysis.mutationOptions({
372373
onSuccess: async (_data, variables) => {
373374
setGitHubError(null); // Clear any previous error on success
374-
toast.success('Analysis started');
375+
toast.success(manualAnalysisAdmissionCopy.successTitle);
375376
refreshAcceptedQueueMutation();
376377
setStartingAnalysisIds(prev => {
377378
const next = new Set(prev);
@@ -388,7 +389,7 @@ export function SecurityAgentPageClient({ organizationId }: SecurityAgentPageCli
388389
'The GitHub App may have been uninstalled. Please check your integrations.',
389390
});
390391
} else {
391-
toast.error('Failed to start analysis', {
392+
toast.error(manualAnalysisAdmissionCopy.failureTitle, {
392393
description: message,
393394
duration: 8000,
394395
});

apps/web/src/components/security-agent/SecurityFindingRow.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
1919
import type { SecurityFinding } from '@kilocode/db/schema';
2020
import { cn } from '@/lib/utils';
2121
import { SeverityBadge } from './SeverityBadge';
22+
import { manualAnalysisAdmissionCopy } from './manual-analysis-admission-copy';
2223

2324
type Outcome = {
2425
icon: typeof CheckCircle2;
@@ -286,7 +287,7 @@ export function SecurityFindingRow({
286287
) : isStartingAnalysis ? (
287288
<Button variant="outline" size="sm" disabled className="gap-1">
288289
<Loader2 className="h-3 w-3 animate-spin" />
289-
Starting
290+
{manualAnalysisAdmissionCopy.pendingLabel}
290291
</Button>
291292
) : finding.analysis?.triage?.suggestedAction === 'manual_review' &&
292293
finding.status === 'open' ? (
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { describe, expect, test } from '@jest/globals';
2+
import { manualAnalysisAdmissionCopy } from './manual-analysis-admission-copy';
3+
4+
describe('manualAnalysisAdmissionCopy', () => {
5+
test('describes manual analysis as queued admission', () => {
6+
expect(manualAnalysisAdmissionCopy).toEqual({
7+
successTitle: 'Analysis queued',
8+
failureTitle: 'Failed to queue analysis',
9+
pendingLabel: 'Queueing',
10+
});
11+
});
12+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const manualAnalysisAdmissionCopy = {
2+
successTitle: 'Analysis queued',
3+
failureTitle: 'Failed to queue analysis',
4+
pendingLabel: 'Queueing',
5+
};

apps/web/src/lib/security-agent/db/security-analysis.test.ts

Lines changed: 8 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ jest.mock('@/lib/drizzle', () => ({
1616
jest.mock('@sentry/nextjs', () => ({ captureException: jest.fn() }));
1717

1818
let cleanupStaleAnalyses: typeof analysisDbModule.cleanupStaleAnalyses;
19-
let isFindingEligibleForAutoAnalysis: typeof analysisDbModule.isFindingEligibleForAutoAnalysis;
2019

2120
beforeAll(async () => {
22-
({ cleanupStaleAnalyses, isFindingEligibleForAutoAnalysis } =
23-
await import('./security-analysis'));
21+
({ cleanupStaleAnalyses } = await import('./security-analysis'));
2422
});
2523

2624
beforeEach(() => {
@@ -41,97 +39,13 @@ describe('cleanupStaleAnalyses', () => {
4139
});
4240
});
4341

44-
describe('isFindingEligibleForAutoAnalysis', () => {
45-
const baseParams = {
46-
findingCreatedAt: '2025-06-01T00:00:00Z',
47-
findingStatus: 'open',
48-
severity: 'high',
49-
ownerAutoAnalysisEnabledAt: '2025-07-01T00:00:00Z',
50-
isAgentEnabled: true,
51-
autoAnalysisEnabled: true,
52-
autoAnalysisMinSeverity: 'high' as const,
53-
};
42+
describe('retired web sync queue policy surface', () => {
43+
it('does not expose obsolete sync queue policy helpers', async () => {
44+
const analysisDb = await import('./security-analysis');
5445

55-
it('rejects findings created before auto_analysis_enabled_at by default', () => {
56-
const result = isFindingEligibleForAutoAnalysis(baseParams);
57-
expect(result.eligible).toBe(false);
58-
});
59-
60-
it('accepts findings created after auto_analysis_enabled_at', () => {
61-
const result = isFindingEligibleForAutoAnalysis({
62-
...baseParams,
63-
findingCreatedAt: '2025-08-01T00:00:00Z',
64-
});
65-
expect(result.eligible).toBe(true);
66-
});
67-
68-
it('accepts pre-existing findings when autoAnalysisIncludeExisting is true', () => {
69-
const result = isFindingEligibleForAutoAnalysis({
70-
...baseParams,
71-
autoAnalysisIncludeExisting: true,
72-
});
73-
expect(result.eligible).toBe(true);
74-
});
75-
76-
it('still rejects non-open findings even with autoAnalysisIncludeExisting', () => {
77-
const result = isFindingEligibleForAutoAnalysis({
78-
...baseParams,
79-
findingStatus: 'fixed',
80-
autoAnalysisIncludeExisting: true,
81-
});
82-
expect(result.eligible).toBe(false);
83-
});
84-
85-
it('still respects severity threshold with autoAnalysisIncludeExisting', () => {
86-
const result = isFindingEligibleForAutoAnalysis({
87-
...baseParams,
88-
severity: 'low',
89-
autoAnalysisMinSeverity: 'high',
90-
autoAnalysisIncludeExisting: true,
91-
});
92-
expect(result.eligible).toBe(false);
93-
});
94-
95-
it('rejects when agent is not enabled even with autoAnalysisIncludeExisting', () => {
96-
const result = isFindingEligibleForAutoAnalysis({
97-
...baseParams,
98-
isAgentEnabled: false,
99-
autoAnalysisIncludeExisting: true,
100-
});
101-
expect(result.eligible).toBe(false);
102-
});
103-
104-
it('treats null severity as eligible with low rank when threshold is "all"', () => {
105-
const result = isFindingEligibleForAutoAnalysis({
106-
...baseParams,
107-
severity: null,
108-
autoAnalysisMinSeverity: 'all',
109-
findingCreatedAt: '2025-08-01T00:00:00Z',
110-
});
111-
expect(result.eligible).toBe(true);
112-
expect(result.severityRank).toBe(3);
113-
});
114-
115-
it('rejects null severity when threshold is stricter than "all"', () => {
116-
const result = isFindingEligibleForAutoAnalysis({
117-
...baseParams,
118-
severity: null,
119-
autoAnalysisMinSeverity: 'high',
120-
findingCreatedAt: '2025-08-01T00:00:00Z',
121-
});
122-
expect(result.eligible).toBe(false);
123-
expect(result.severityRank).toBe(3);
124-
});
125-
126-
it('treats null severity as eligible when threshold is medium', () => {
127-
const result = isFindingEligibleForAutoAnalysis({
128-
...baseParams,
129-
severity: null,
130-
autoAnalysisMinSeverity: 'medium',
131-
findingCreatedAt: '2025-08-01T00:00:00Z',
132-
});
133-
// low rank (3) > medium max rank (2), so not eligible
134-
expect(result.eligible).toBe(false);
135-
expect(result.severityRank).toBe(3);
46+
expect('getOwnerAutoAnalysisEnabledAt' in analysisDb).toBe(false);
47+
expect('isFindingEligibleForAutoAnalysis' in analysisDb).toBe(false);
48+
expect('syncAutoAnalysisQueueForFinding' in analysisDb).toBe(false);
49+
expect('dequeueSupersededFindings' in analysisDb).toBe(false);
13650
});
13751
});

0 commit comments

Comments
 (0)