feat(integration): WP-6 AI triage dialogs — useAITriage, progress overlay, split dialog wiring
Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
402588448b
commit
ed04daea01
@@ -12,6 +12,7 @@ import {
|
||||
useIssueFiltering,
|
||||
useAutoFix,
|
||||
useBulkOperations,
|
||||
useAITriage,
|
||||
} from "./github-issues/hooks";
|
||||
import { useAnalyzePreview } from "./github-issues/hooks/useAnalyzePreview";
|
||||
import {
|
||||
@@ -23,6 +24,9 @@ import {
|
||||
InvestigationDialog,
|
||||
BatchReviewWizard,
|
||||
BulkActionBar,
|
||||
TriageProgressOverlay,
|
||||
IssueSplitDialog,
|
||||
EnrichmentCommentPreview,
|
||||
} from "./github-issues/components";
|
||||
import { GitHubSetupModal } from "./GitHubSetupModal";
|
||||
import type { GitHubIssue } from "../../shared/types";
|
||||
@@ -135,6 +139,9 @@ export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesP
|
||||
[executeBulk, selectedIssueNumbers],
|
||||
);
|
||||
|
||||
// AI Triage
|
||||
const aiTriage = useAITriage(selectedProject?.id ?? '');
|
||||
|
||||
// Clear selection when filters change
|
||||
useEffect(() => {
|
||||
setSelectedIssueNumbers(new Set());
|
||||
@@ -289,6 +296,10 @@ export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesP
|
||||
autoFixQueueItem={getAutoFixQueueItem(selectedIssue.number)}
|
||||
enrichment={enrichments[String(selectedIssue.number)] ?? null}
|
||||
onTransition={handleTransition}
|
||||
onAITriage={() => aiTriage.runEnrichment(selectedIssue.number)}
|
||||
onImproveIssue={() => aiTriage.runEnrichment(selectedIssue.number)}
|
||||
onSplitIssue={() => aiTriage.runSplitSuggestion(selectedIssue.number)}
|
||||
isAIBusy={aiTriage.isTriaging}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message="Select an issue to view details" />
|
||||
@@ -321,6 +332,24 @@ export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesP
|
||||
isApproving={isApproving}
|
||||
/>
|
||||
|
||||
{/* AI Triage Progress */}
|
||||
{(aiTriage.enrichmentProgress || aiTriage.triageProgress) && (
|
||||
<TriageProgressOverlay
|
||||
progress={aiTriage.enrichmentProgress ?? aiTriage.triageProgress ?? { progress: 0, message: '' }}
|
||||
onCancel={() => { /* cancel handled by store */ }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Split Dialog */}
|
||||
{aiTriage.splitSuggestion && (
|
||||
<IssueSplitDialog
|
||||
suggestion={aiTriage.splitSuggestion}
|
||||
progress={aiTriage.splitProgress}
|
||||
onConfirm={aiTriage.confirmSplit}
|
||||
onCancel={() => { /* reset split state */ }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* GitHub Setup Modal - shown when GitHub module is not configured */}
|
||||
{selectedProject && (
|
||||
<GitHubSetupModal
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { TriageProgressOverlay } from '../components/TriageProgressOverlay';
|
||||
import { IssueSplitDialog } from '../components/IssueSplitDialog';
|
||||
import { EnrichmentCommentPreview } from '../components/EnrichmentCommentPreview';
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../shared/constants/ai-triage', () => ({
|
||||
ENRICHMENT_COMMENT_FOOTER: '---\n_AI-generated_',
|
||||
}));
|
||||
|
||||
describe('AI Triage integration', () => {
|
||||
it('TriageProgressOverlay renders progress bar and message', () => {
|
||||
const progress = { progress: 60, message: 'Enriching issue...' };
|
||||
render(<TriageProgressOverlay progress={progress} onCancel={vi.fn()} />);
|
||||
expect(screen.getByText('Enriching issue...')).toBeDefined();
|
||||
expect(screen.getByRole('progressbar')).toBeDefined();
|
||||
});
|
||||
|
||||
it('TriageProgressOverlay cancel button calls onCancel', () => {
|
||||
const onCancel = vi.fn();
|
||||
const progress = { progress: 30, message: 'Working...' };
|
||||
render(<TriageProgressOverlay progress={progress} onCancel={onCancel} />);
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
expect(onCancel).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('IssueSplitDialog renders sub-issues from suggestion', () => {
|
||||
const suggestion = {
|
||||
issueNumber: 42,
|
||||
rationale: 'This issue should be split',
|
||||
subIssues: [
|
||||
{ title: 'Sub-issue A', body: 'Body A', labels: ['bug'] },
|
||||
{ title: 'Sub-issue B', body: 'Body B', labels: [] },
|
||||
],
|
||||
};
|
||||
render(
|
||||
<IssueSplitDialog
|
||||
suggestion={suggestion}
|
||||
progress={null}
|
||||
onConfirm={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByDisplayValue('Sub-issue A')).toBeDefined();
|
||||
expect(screen.getByDisplayValue('Sub-issue B')).toBeDefined();
|
||||
expect(screen.getByText('This issue should be split')).toBeDefined();
|
||||
});
|
||||
|
||||
it('IssueSplitDialog onConfirm passes sub-issues', () => {
|
||||
const onConfirm = vi.fn();
|
||||
const suggestion = {
|
||||
issueNumber: 42,
|
||||
rationale: 'Split needed',
|
||||
subIssues: [{ title: 'Sub A', body: 'Body A', labels: [] }],
|
||||
};
|
||||
render(
|
||||
<IssueSplitDialog
|
||||
suggestion={suggestion}
|
||||
progress={null}
|
||||
onConfirm={onConfirm}
|
||||
onCancel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
// Click the confirm button
|
||||
const confirmBtn = screen.getByRole('button', { name: 'common:issueSplit.confirm' });
|
||||
fireEvent.click(confirmBtn);
|
||||
expect(onConfirm).toHaveBeenCalledWith(42, [{ title: 'Sub A', body: 'Body A', labels: [] }]);
|
||||
});
|
||||
|
||||
it('EnrichmentCommentPreview renders content and post button', () => {
|
||||
render(
|
||||
<EnrichmentCommentPreview
|
||||
content="AI analysis comment"
|
||||
onPost={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByDisplayValue('AI analysis comment')).toBeDefined();
|
||||
});
|
||||
|
||||
it('EnrichmentCommentPreview calls onCancel on discard', () => {
|
||||
const onCancel = vi.fn();
|
||||
render(
|
||||
<EnrichmentCommentPreview content="test" onPost={vi.fn()} onCancel={onCancel} />,
|
||||
);
|
||||
const cancelBtn = screen.getByRole('button', { name: 'common:enrichmentComment.cancel' });
|
||||
fireEvent.click(cancelBtn);
|
||||
expect(onCancel).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user