Skip to content
Merged
5 changes: 1 addition & 4 deletions tests/e2e/test_dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ test.describe('Dashboard - Sprint 10 Features', () => {
await expect(page.locator('[data-testid="review-score-chart"]')).toBeAttached();
});

test.skip('should display quality gates panel', async () => {
// SKIP: Quality gates panel requires task selection and is currently disabled in Dashboard
// This test will be enabled when quality gates feature is fully implemented

test('should display quality gates panel', async () => {
// Navigate to quality gates section
const qualityGatesPanel = page.locator('[data-testid="quality-gates-panel"]');

Expand Down
25 changes: 15 additions & 10 deletions web-ui/src/api/qualityGates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,26 @@ const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
* Fetch quality gate status for a task
*
* @param taskId - Task ID to get quality gate status for
* @param projectId - Optional project ID for multi-project scoping
* @returns Promise resolving to QualityGateStatus or null if not found
* @throws Error if request fails
*/
export async function fetchQualityGateStatus(
taskId: number
taskId: number,
projectId?: number
): Promise<QualityGateStatus | null> {
const response = await fetch(
`${API_BASE_URL}/api/tasks/${taskId}/quality-gates`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
// Build URL with optional project_id query parameter
const url = new URL(`${API_BASE_URL}/api/tasks/${taskId}/quality-gates`);
if (projectId !== undefined && projectId > 0) {
url.searchParams.append('project_id', projectId.toString());
}

const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

if (response.status === 404) {
return null; // No quality gate status exists yet
Expand Down
8 changes: 4 additions & 4 deletions web-ui/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { SessionStatus } from './SessionStatus';
import CheckpointList from './checkpoints/CheckpointList';
import CostDashboard from './metrics/CostDashboard';
import ReviewSummary from './reviews/ReviewSummary';
import { QualityGatesPanel } from './quality-gates';
import TaskStats from './tasks/TaskStats';

interface DashboardProps {
Expand Down Expand Up @@ -436,13 +437,12 @@ export default function Dashboard({ projectId }: DashboardProps) {
</div>

{/* Quality Gates Panel (Sprint 10) */}
{/* Note: QualityGateStatus requires taskId, not projectId. Disabled for now until task is selected */}
{/* <div className="mb-6" data-testid="quality-gates-panel">
<div className="mb-6" data-testid="quality-gates-panel">
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-4">βœ… Quality Gates</h2>
<QualityGateStatus taskId={0} />
<QualityGatesPanel projectId={projectId} tasks={tasks} />
</div>
</div> */}
</div>

{/* Checkpoint Panel (Sprint 10) */}
<div className="mb-6" data-testid="checkpoint-panel">
Expand Down
62 changes: 62 additions & 0 deletions web-ui/src/components/quality-gates/GateStatusIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* GateStatusIndicator Component
*
* Displays status for an individual quality gate with icon, name, and status badge
* Used in QualityGatesPanel to show overview of all gate types
*/

'use client';

import type { GateTypeE2E, QualityGateStatusValue } from '@/types/qualityGates';
import { getGateIcon, getGateName, getStatusClasses, getStatusIcon } from '@/lib/qualityGateUtils';

interface GateStatusIndicatorProps {
gateType: GateTypeE2E;
status: QualityGateStatusValue;
testId?: string;
}

/**
* GateStatusIndicator Component
*
* Shows individual gate status in a card layout with proper accessibility
*/
export default function GateStatusIndicator({
gateType,
status,
testId,
}: GateStatusIndicatorProps) {
const gateName = getGateName(gateType);
const statusText = status ?? 'pending';

return (
<div
data-testid={testId || `gate-${gateType}`}
className="flex flex-col items-center justify-center p-4 bg-white rounded-lg border border-gray-200 shadow-sm hover:shadow-md transition-shadow"
role="listitem"
aria-label={`${gateName} gate: ${statusText}`}
>
{/* Gate Icon */}
<div className="text-3xl mb-2" aria-hidden="true">
{getGateIcon(gateType)}
</div>

{/* Gate Name */}
<div className="text-sm font-medium text-gray-900 mb-2 text-center">
{gateName}
</div>

{/* Status Badge */}
<div
className={`flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-full border ${getStatusClasses(
status
)}`}
role="status"
aria-label={`Status: ${statusText}`}
>
<span aria-hidden="true">{getStatusIcon(status)}</span>
<span>{statusText}</span>
</div>
</div>
);
}
68 changes: 1 addition & 67 deletions web-ui/src/components/quality-gates/QualityGateStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { useState, useEffect, useCallback } from 'react';
import type {
QualityGateStatus as QualityGateStatusType,
QualityGateFailure,
QualityGateStatusValue,
} from '@/types/qualityGates';
import { fetchQualityGateStatus, triggerQualityGates } from '@/api/qualityGates';
import { getStatusClasses, getSeverityClasses, getGateIcon, getStatusIcon } from '@/lib/qualityGateUtils';

interface QualityGateStatusProps {
taskId: number;
Expand Down Expand Up @@ -85,72 +85,6 @@ export default function QualityGateStatus({
}
};

// Get status badge classes
const getStatusClasses = (statusValue: QualityGateStatusValue): string => {
switch (statusValue) {
case 'passed':
return 'bg-green-100 text-green-800 border-green-300';
case 'failed':
return 'bg-red-100 text-red-800 border-red-300';
case 'running':
return 'bg-yellow-100 text-yellow-800 border-yellow-300';
case 'pending':
return 'bg-gray-100 text-gray-800 border-gray-300';
default:
return 'bg-gray-100 text-gray-800 border-gray-300';
}
};

// Get severity badge classes
const getSeverityClasses = (severity: string): string => {
switch (severity) {
case 'critical':
return 'bg-red-100 text-red-900 border-red-300';
case 'high':
return 'bg-orange-100 text-orange-900 border-orange-300';
case 'medium':
return 'bg-yellow-100 text-yellow-900 border-yellow-300';
case 'low':
return 'bg-blue-100 text-blue-900 border-blue-300';
default:
return 'bg-gray-100 text-gray-900 border-gray-300';
}
};

// Get gate icon
const getGateIcon = (gate: string): string => {
switch (gate) {
case 'tests':
return 'πŸ§ͺ';
case 'type_check':
return 'πŸ“';
case 'coverage':
return 'πŸ“Š';
case 'code_review':
return 'πŸ”';
case 'linting':
return '✨';
default:
return 'βš™οΈ';
}
};

// Get status icon
const getStatusIcon = (statusValue: QualityGateStatusValue): string => {
switch (statusValue) {
case 'passed':
return 'βœ…';
case 'failed':
return '❌';
case 'running':
return '⏳';
case 'pending':
return '⏸️';
default:
return '❓';
}
};

// Loading state
if (loading) {
return (
Expand Down
Loading
Loading