Revert "feat: Enterprise DSS architecture implementation"
Some checks failed
DSS Project Analysis / dss-context-update (push) Has been cancelled

This reverts commit 9dbd56271e.
This commit is contained in:
DSS
2025-12-11 09:59:45 -03:00
parent 9dbd56271e
commit 44cea9443b
27 changed files with 397 additions and 3887 deletions

View File

@@ -1,424 +0,0 @@
import { JSX } from 'preact';
import { useState, useEffect } from 'preact/hooks';
import { Card, CardHeader, CardContent } from './base/Card';
import { Badge } from './base/Badge';
import { Button } from './base/Button';
import { Spinner } from './base/Spinner';
interface ProjectMetrics {
project: string;
total_files: number;
passed_files: number;
failed_files: number;
total_errors: number;
total_warnings: number;
rules_version: string;
last_updated: string;
adoption_score: number;
}
interface PortfolioData {
total_projects: number;
projects_passing: number;
projects_failing: number;
total_errors: number;
total_warnings: number;
average_adoption_score: number;
projects: ProjectMetrics[];
}
interface ViolationLocation {
file: string;
rule: string;
line: number;
column: number;
severity: string;
}
interface TrendDataPoint {
date: string;
errors: number;
warnings: number;
pass_rate: number;
}
const API_BASE = '/api/metrics';
export function PortfolioDashboard(): JSX.Element {
const [loading, setLoading] = useState(true);
const [portfolio, setPortfolio] = useState<PortfolioData | null>(null);
const [selectedProject, setSelectedProject] = useState<string | null>(null);
const [projectDetails, setProjectDetails] = useState<any>(null);
const [trends, setTrends] = useState<TrendDataPoint[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadPortfolio();
loadTrends();
}, []);
async function loadPortfolio() {
setLoading(true);
setError(null);
try {
const response = await fetch(`${API_BASE}/portfolio?days=30`);
if (!response.ok) throw new Error('Failed to load portfolio data');
const data = await response.json();
setPortfolio(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}
async function loadTrends() {
try {
const response = await fetch(`${API_BASE}/trends?days=30`);
if (!response.ok) return;
const data = await response.json();
setTrends(data.data || []);
} catch (err) {
console.error('Failed to load trends:', err);
}
}
async function loadProjectDetails(projectName: string) {
setSelectedProject(projectName);
try {
const response = await fetch(`${API_BASE}/projects/${encodeURIComponent(projectName)}`);
if (!response.ok) throw new Error('Failed to load project details');
const data = await response.json();
setProjectDetails(data);
} catch (err) {
console.error('Failed to load project details:', err);
setProjectDetails(null);
}
}
if (loading) {
return (
<div className="portfolio-dashboard portfolio-loading">
<Spinner size="lg" />
<span>Loading portfolio metrics...</span>
</div>
);
}
if (error) {
return (
<div className="portfolio-dashboard portfolio-error">
<Card variant="bordered" padding="lg">
<CardContent>
<div className="error-message">
<Badge variant="error">Error</Badge>
<p>{error}</p>
<Button variant="outline" onClick={loadPortfolio}>Retry</Button>
</div>
</CardContent>
</Card>
</div>
);
}
if (!portfolio) {
return (
<div className="portfolio-dashboard">
<Card variant="bordered" padding="lg">
<CardContent>
<p className="text-muted">No portfolio data available. Run DSS validation in your CI pipelines to collect metrics.</p>
</CardContent>
</Card>
</div>
);
}
return (
<div className="portfolio-dashboard">
<div className="portfolio-header">
<h1>Design System Portfolio</h1>
<p className="subtitle">Adoption metrics across {portfolio.total_projects} projects</p>
<Button variant="ghost" size="sm" onClick={loadPortfolio}>Refresh</Button>
</div>
{/* Portfolio Summary */}
<div className="metrics-grid portfolio-metrics">
<MetricCard
label="Total Projects"
value={portfolio.total_projects}
/>
<MetricCard
label="Passing"
value={portfolio.projects_passing}
variant="success"
/>
<MetricCard
label="Failing"
value={portfolio.projects_failing}
variant={portfolio.projects_failing > 0 ? 'error' : 'default'}
/>
<MetricCard
label="Adoption Score"
value={`${portfolio.average_adoption_score}%`}
variant={portfolio.average_adoption_score >= 80 ? 'success' : portfolio.average_adoption_score >= 60 ? 'warning' : 'error'}
/>
<MetricCard
label="Total Errors"
value={portfolio.total_errors}
variant={portfolio.total_errors > 0 ? 'error' : 'success'}
/>
<MetricCard
label="Total Warnings"
value={portfolio.total_warnings}
variant={portfolio.total_warnings > 0 ? 'warning' : 'default'}
/>
</div>
{/* Trend Chart (simple text-based for now) */}
{trends.length > 0 && (
<Card variant="bordered" padding="md">
<CardHeader title="30-Day Trend" subtitle="Errors and warnings over time" />
<CardContent>
<div className="trend-chart">
{trends.slice(-7).map((point, idx) => (
<div key={idx} className="trend-bar">
<div className="trend-date">{point.date.slice(5)}</div>
<div className="trend-values">
<span className="trend-errors" title={`${point.errors} errors`}>
{point.errors > 0 && '●'.repeat(Math.min(point.errors, 10))}
</span>
<span className="trend-warnings" title={`${point.warnings} warnings`}>
{point.warnings > 0 && '○'.repeat(Math.min(Math.ceil(point.warnings / 10), 10))}
</span>
</div>
<div className="trend-rate">{point.pass_rate}%</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Projects Table */}
<Card variant="bordered" padding="md">
<CardHeader
title="Projects"
subtitle="Click a project to see violation details"
/>
<CardContent>
<div className="projects-table">
<div className="table-header">
<span className="col-project">Project</span>
<span className="col-score">Score</span>
<span className="col-files">Files</span>
<span className="col-errors">Errors</span>
<span className="col-warnings">Warnings</span>
<span className="col-version">Rules</span>
<span className="col-updated">Updated</span>
</div>
{portfolio.projects.map(project => (
<div
key={project.project}
className={`table-row ${selectedProject === project.project ? 'selected' : ''}`}
onClick={() => loadProjectDetails(project.project)}
>
<span className="col-project">{formatProjectName(project.project)}</span>
<span className="col-score">
<Badge
variant={project.adoption_score >= 80 ? 'success' : project.adoption_score >= 60 ? 'warning' : 'error'}
size="sm"
>
{project.adoption_score.toFixed(0)}%
</Badge>
</span>
<span className="col-files">{project.passed_files}/{project.total_files}</span>
<span className="col-errors">
{project.total_errors > 0 ? (
<Badge variant="error" size="sm">{project.total_errors}</Badge>
) : (
<span className="text-success">0</span>
)}
</span>
<span className="col-warnings">
{project.total_warnings > 0 ? (
<Badge variant="warning" size="sm">{project.total_warnings}</Badge>
) : (
<span className="text-muted">0</span>
)}
</span>
<span className="col-version">{project.rules_version}</span>
<span className="col-updated">{formatTimeAgo(project.last_updated)}</span>
</div>
))}
</div>
</CardContent>
</Card>
{/* Project Details Panel */}
{selectedProject && projectDetails && (
<ProjectDetailsPanel
project={selectedProject}
details={projectDetails}
onClose={() => setSelectedProject(null)}
/>
)}
</div>
);
}
interface MetricCardProps {
label: string;
value: string | number;
variant?: 'default' | 'success' | 'warning' | 'error';
}
function MetricCard({ label, value, variant = 'default' }: MetricCardProps): JSX.Element {
return (
<Card variant="bordered" padding="md">
<div className={`metric-display metric-${variant}`}>
<span className="metric-label">{label}</span>
<span className="metric-value">{value}</span>
</div>
</Card>
);
}
interface ProjectDetailsPanelProps {
project: string;
details: any;
onClose: () => void;
}
function ProjectDetailsPanel({ project, details, onClose }: ProjectDetailsPanelProps): JSX.Element {
const [activeTab, setActiveTab] = useState<'overview' | 'violations' | 'history'>('overview');
return (
<Card variant="elevated" padding="lg" className="project-details-panel">
<CardHeader
title={formatProjectName(project)}
subtitle="Detailed metrics and violations"
action={<Button variant="ghost" size="sm" onClick={onClose}>×</Button>}
/>
<CardContent>
{/* Tabs */}
<div className="tabs">
<button
className={`tab ${activeTab === 'overview' ? 'active' : ''}`}
onClick={() => setActiveTab('overview')}
>
Overview
</button>
<button
className={`tab ${activeTab === 'violations' ? 'active' : ''}`}
onClick={() => setActiveTab('violations')}
>
Violations
</button>
<button
className={`tab ${activeTab === 'history' ? 'active' : ''}`}
onClick={() => setActiveTab('history')}
>
History
</button>
</div>
{/* Tab Content */}
{activeTab === 'overview' && details.latest && (
<div className="tab-content">
<div className="detail-grid">
<div className="detail-item">
<span className="detail-label">Branch</span>
<span className="detail-value">{details.latest.branch}</span>
</div>
<div className="detail-item">
<span className="detail-label">Commit</span>
<span className="detail-value">{details.latest.commit?.slice(0, 7)}</span>
</div>
<div className="detail-item">
<span className="detail-label">Rules Version</span>
<span className="detail-value">{details.latest.rules_version}</span>
</div>
<div className="detail-item">
<span className="detail-label">Adoption Score</span>
<span className="detail-value">{details.latest.adoption_score?.toFixed(1)}%</span>
</div>
</div>
{/* Violations by Rule */}
{details.violations_by_rule && Object.keys(details.violations_by_rule).length > 0 && (
<div className="violations-breakdown">
<h4>Violations by Rule</h4>
{Object.entries(details.violations_by_rule).map(([rule, count]) => (
<div key={rule} className="rule-row">
<span className="rule-name">{rule}</span>
<Badge variant="error" size="sm">{count as number}</Badge>
</div>
))}
</div>
)}
</div>
)}
{activeTab === 'violations' && (
<div className="tab-content violations-list">
{details.violation_locations?.length === 0 ? (
<p className="text-muted">No violations found</p>
) : (
details.violation_locations?.map((v: ViolationLocation, idx: number) => (
<div key={idx} className="violation-item">
<div className="violation-location">
<code>{v.file}:{v.line}:{v.column}</code>
</div>
<div className="violation-rule">
<Badge variant={v.severity === 'error' ? 'error' : 'warning'} size="sm">
{v.rule}
</Badge>
</div>
</div>
))
)}
</div>
)}
{activeTab === 'history' && (
<div className="tab-content history-list">
{details.history?.map((h: any, idx: number) => (
<div key={idx} className="history-item">
<span className="history-commit">{h.commit?.slice(0, 7)}</span>
<span className="history-branch">{h.branch}</span>
<span className="history-errors">
{h.errors > 0 ? <Badge variant="error" size="sm">{h.errors}</Badge> : '0'}
</span>
<span className="history-time">{formatTimeAgo(h.timestamp)}</span>
</div>
))}
</div>
)}
</CardContent>
</Card>
);
}
// Utility functions
function formatProjectName(name: string): string {
// Format "org/repo" or just "repo"
const parts = name.split('/');
return parts[parts.length - 1];
}
function formatTimeAgo(timestamp: string): string {
if (!timestamp) return 'Unknown';
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
}
export default PortfolioDashboard;

View File

@@ -1,360 +0,0 @@
/* Portfolio Dashboard Styles */
.portfolio-dashboard {
padding: var(--spacing-lg);
max-width: 1400px;
margin: 0 auto;
}
.portfolio-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.portfolio-header h1 {
font-size: var(--font-size-2xl);
font-weight: 600;
margin: 0;
}
.portfolio-header .subtitle {
color: var(--color-text-muted);
margin: 0;
flex: 1;
}
.portfolio-loading,
.portfolio-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 400px;
gap: var(--spacing-md);
}
.portfolio-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.metric-display {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.metric-label {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin-bottom: var(--spacing-xs);
}
.metric-value {
font-size: var(--font-size-2xl);
font-weight: 700;
}
.metric-success .metric-value {
color: var(--color-success);
}
.metric-warning .metric-value {
color: var(--color-warning);
}
.metric-error .metric-value {
color: var(--color-error);
}
/* Trend Chart */
.trend-chart {
display: flex;
gap: var(--spacing-sm);
overflow-x: auto;
padding: var(--spacing-sm) 0;
}
.trend-bar {
display: flex;
flex-direction: column;
align-items: center;
min-width: 80px;
padding: var(--spacing-xs);
border-radius: var(--radius-sm);
background: var(--color-bg-secondary);
}
.trend-date {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
margin-bottom: var(--spacing-xs);
}
.trend-values {
display: flex;
flex-direction: column;
align-items: center;
min-height: 40px;
}
.trend-errors {
color: var(--color-error);
font-size: 10px;
letter-spacing: -2px;
}
.trend-warnings {
color: var(--color-warning);
font-size: 8px;
letter-spacing: -2px;
}
.trend-rate {
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--color-text);
}
/* Projects Table */
.projects-table {
display: flex;
flex-direction: column;
gap: 2px;
}
.table-header,
.table-row {
display: grid;
grid-template-columns: 2fr 80px 80px 80px 80px 80px 100px;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
align-items: center;
}
.table-header {
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--color-text-muted);
text-transform: uppercase;
border-bottom: 1px solid var(--color-border);
}
.table-row {
border-radius: var(--radius-sm);
cursor: pointer;
transition: background-color 0.15s ease;
}
.table-row:hover {
background: var(--color-bg-secondary);
}
.table-row.selected {
background: var(--color-bg-tertiary);
border-left: 3px solid var(--color-primary);
}
.col-project {
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.col-score,
.col-files,
.col-errors,
.col-warnings,
.col-version,
.col-updated {
text-align: center;
font-size: var(--font-size-sm);
}
.col-updated {
color: var(--color-text-muted);
}
/* Project Details Panel */
.project-details-panel {
margin-top: var(--spacing-lg);
animation: slideUp 0.2s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.tabs {
display: flex;
gap: var(--spacing-xs);
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--spacing-md);
}
.tab {
padding: var(--spacing-sm) var(--spacing-md);
border: none;
background: none;
cursor: pointer;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
border-bottom: 2px solid transparent;
transition: all 0.15s ease;
}
.tab:hover {
color: var(--color-text);
}
.tab.active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
}
.tab-content {
padding: var(--spacing-md) 0;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.detail-item {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.detail-label {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
text-transform: uppercase;
}
.detail-value {
font-weight: 500;
}
.violations-breakdown {
margin-top: var(--spacing-lg);
}
.violations-breakdown h4 {
font-size: var(--font-size-sm);
font-weight: 600;
margin-bottom: var(--spacing-sm);
}
.rule-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-xs) 0;
border-bottom: 1px solid var(--color-border-light);
}
.rule-name {
font-family: var(--font-mono);
font-size: var(--font-size-sm);
}
/* Violations List */
.violations-list {
max-height: 400px;
overflow-y: auto;
}
.violation-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm);
border-bottom: 1px solid var(--color-border-light);
}
.violation-location code {
font-size: var(--font-size-sm);
color: var(--color-text);
}
/* History List */
.history-list {
max-height: 300px;
overflow-y: auto;
}
.history-item {
display: grid;
grid-template-columns: 80px 100px 60px 1fr;
gap: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--color-border-light);
font-size: var(--font-size-sm);
}
.history-commit {
font-family: var(--font-mono);
}
.history-branch {
color: var(--color-text-muted);
overflow: hidden;
text-overflow: ellipsis;
}
.history-time {
text-align: right;
color: var(--color-text-muted);
}
/* Text utilities */
.text-success {
color: var(--color-success);
}
.text-warning {
color: var(--color-warning);
}
.text-error {
color: var(--color-error);
}
.text-muted {
color: var(--color-text-muted);
}
/* Responsive */
@media (max-width: 768px) {
.table-header,
.table-row {
grid-template-columns: 1fr 60px 60px 60px;
}
.col-version,
.col-updated,
.col-warnings {
display: none;
}
.portfolio-metrics {
grid-template-columns: repeat(2, 1fr);
}
}

View File

@@ -7,10 +7,8 @@ import { Input, Select } from '../components/base/Input';
import { Spinner } from '../components/base/Spinner';
import { endpoints } from '../api/client';
import { currentProject } from '../state/project';
import { PortfolioDashboard } from '../components/PortfolioDashboard';
import type { TokenDrift, Component, FigmaExtractResult } from '../api/types';
import './Workdesk.css';
import '../styles/portfolio.css';
interface UIWorkdeskProps {
activeTool: string | null;
@@ -28,7 +26,6 @@ export default function UIWorkdesk({ activeTool }: UIWorkdeskProps) {
'code-generator': <CodeGeneratorTool />,
'quick-wins': <QuickWinsTool />,
'token-drift': <TokenDriftTool />,
'portfolio': <PortfolioDashboard />,
};
return toolViews[activeTool] || <ToolPlaceholder name={activeTool} />;