Skip to content

Commit ccf9035

Browse files
committed
feat: cleanup
1 parent 7fbd8cd commit ccf9035

File tree

9 files changed

+355
-65
lines changed

9 files changed

+355
-65
lines changed

public/locales/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,13 @@
383383
"noResources": "No Resources",
384384
"inactive": "Inactive",
385385
"activate": "Activate",
386-
"healthy": "Healthy"
386+
"healthy": "Healthy",
387+
"hoverContent": {
388+
"totalResources": "Total Resources",
389+
"healthy": "Healthy",
390+
"creating": "Creating",
391+
"failing": "Failing"
392+
}
387393
},
388394
"GitOpsHint": {
389395
"title": "Flux",
Lines changed: 64 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React, { useMemo } from 'react';
2+
import { useTranslation } from 'react-i18next';
23
import { RadarChart } from '@ui5/webcomponents-react-charts';
3-
import { ManagedResourceItem, Condition } from '../../lib/shared/types';
4+
import { ManagedResourceItem } from '../../lib/shared/types';
5+
import { calculateCrossplaneHoverData, HINT_COLORS } from './calculations';
6+
import { LegendSection } from './LegendSection';
47

58
interface CrossplaneHoverContentProps {
69
allItems: ManagedResourceItem[];
@@ -11,82 +14,81 @@ export const CrossplaneHoverContent: React.FC<CrossplaneHoverContentProps> = ({
1114
allItems,
1215
enabled,
1316
}) => {
14-
// Memoize resource type health calculations
15-
const { resourceTypeHealth, resourceTypeTotal } = useMemo(() => {
16-
const typeHealth: Record<string, number> = {};
17-
const typeTotal: Record<string, number> = {};
18-
19-
allItems.forEach((item: ManagedResourceItem) => {
20-
const type = item.kind || 'Unknown';
21-
typeTotal[type] = (typeTotal[type] || 0) + 1;
22-
const conditions = item.status?.conditions || [];
23-
const ready = conditions.find((c: Condition) => c.type === 'Ready' && c.status === 'True');
24-
const synced = conditions.find((c: Condition) => c.type === 'Synced' && c.status === 'True');
25-
if (ready && synced) {
26-
typeHealth[type] = (typeHealth[type] || 0) + 1;
27-
}
28-
});
17+
const { t } = useTranslation();
2918

30-
return { resourceTypeHealth: typeHealth, resourceTypeTotal: typeTotal };
31-
}, [allItems]);
19+
// Calculate all statistics using the dedicated calculation function
20+
const { resourceTypeStats, overallStats } = useMemo(() =>
21+
calculateCrossplaneHoverData(allItems),
22+
[allItems]
23+
);
3224

33-
// Memoize radar chart dataset
25+
// Prepare radar chart dataset
3426
const radarDataset = useMemo(() => {
35-
return Object.keys(resourceTypeTotal).map(type => {
36-
const total = resourceTypeTotal[type];
37-
const healthy = resourceTypeHealth[type] || 0;
38-
39-
// Count creating resources (no conditions yet or unknown status)
40-
const creating = allItems.filter((item: ManagedResourceItem) => {
41-
if (item.kind !== type) return false;
42-
const conditions = item.status?.conditions || [];
43-
const hasReadyCondition = conditions.some((c: Condition) => c.type === 'Ready');
44-
const hasSyncedCondition = conditions.some((c: Condition) => c.type === 'Synced');
45-
return !hasReadyCondition || !hasSyncedCondition;
46-
}).length;
47-
48-
return {
49-
type,
50-
healthy: Math.round((healthy / total) * 100),
51-
creating: Math.round((creating / total) * 100)
52-
};
53-
});
54-
}, [allItems, resourceTypeHealth, resourceTypeTotal]);
27+
return resourceTypeStats.map(stats => ({
28+
type: stats.type,
29+
healthy: stats.healthyPercentage,
30+
creating: stats.creatingPercentage,
31+
unhealthy: stats.unhealthyPercentage
32+
}));
33+
}, [resourceTypeStats]);
5534

56-
if (!enabled || radarDataset.length === 0) {
35+
if (!enabled || resourceTypeStats.length === 0) {
5736
return null;
5837
}
5938

39+
// Prepare legend items with translations
40+
const legendItems = [
41+
{
42+
label: t('Hints.CrossplaneHint.hoverContent.healthy'),
43+
count: overallStats.healthy,
44+
color: HINT_COLORS.healthy
45+
},
46+
{
47+
label: t('Hints.CrossplaneHint.hoverContent.creating'),
48+
count: overallStats.creating,
49+
color: HINT_COLORS.creating
50+
},
51+
{
52+
label: t('Hints.CrossplaneHint.hoverContent.failing'),
53+
count: overallStats.unhealthy,
54+
color: HINT_COLORS.unhealthy
55+
}
56+
];
6057
return (
6158
<div style={{
6259
width: '100%',
63-
height: 300,
6460
display: 'flex',
65-
justifyContent: 'center',
66-
alignItems: 'center',
61+
flexDirection: 'column',
62+
alignItems: 'center',
6763
margin: '1rem 0',
6864
overflow: 'visible'
6965
}}>
70-
<RadarChart
71-
dataset={radarDataset}
72-
dimensions={[{ accessor: 'type' }]}
73-
measures={[
74-
{
75-
accessor: 'healthy',
76-
color: '#28a745',
77-
hideDataLabel: true,
78-
label: 'Healthy (%)'
79-
},
80-
{
81-
accessor: 'creating',
82-
color: '#fd7e14',
83-
hideDataLabel: true,
84-
label: 'Creating (%)'
85-
}
86-
]}
87-
style={{ width: '100%', height: '100%', minWidth: 280, minHeight: 280 }}
88-
noLegend={false}
66+
<LegendSection
67+
title={`${overallStats.total} ${t('Hints.CrossplaneHint.hoverContent.totalResources')}`}
68+
items={legendItems}
8969
/>
70+
<div style={{
71+
width: '100%',
72+
height: 300,
73+
display: 'flex',
74+
justifyContent: 'center',
75+
alignItems: 'center'
76+
}}>
77+
<RadarChart
78+
dataset={radarDataset}
79+
dimensions={[{ accessor: 'type' }]}
80+
measures={[
81+
{
82+
accessor: 'healthy',
83+
color: HINT_COLORS.healthy,
84+
hideDataLabel: true,
85+
label: t('Hints.CrossplaneHint.hoverContent.healthy') + ' (%)'
86+
}
87+
]}
88+
style={{ width: '100%', height: '100%', minWidth: 280, minHeight: 280 }}
89+
noLegend={true}
90+
/>
91+
</div>
9092
</div>
9193
);
9294
};

src/components/Hints/GenericHint.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
22
import { Card, CardHeader, Button } from '@ui5/webcomponents-react';
33
import { useTranslation } from 'react-i18next';
44
import cx from 'clsx';
5-
import { MultiPercentageBar } from '../Shared/MultiPercentageBar';
5+
import { MultiPercentageBar } from './MultiPercentageBar';
66
import { GenericHintProps } from './types';
77
import { styles } from './Hints';
88

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
3+
interface LegendItem {
4+
label: string;
5+
count: number;
6+
color: string;
7+
}
8+
9+
interface LegendSectionProps {
10+
title: string;
11+
items: LegendItem[];
12+
style?: React.CSSProperties;
13+
}
14+
15+
export const LegendSection: React.FC<LegendSectionProps> = ({
16+
title,
17+
items,
18+
style
19+
}) => {
20+
return (
21+
<div style={{
22+
marginBottom: '1rem',
23+
padding: '0.75rem',
24+
borderRadius: '6px',
25+
backgroundColor: 'white',
26+
border: '1px solid #e1e5e9',
27+
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
28+
width: 'fit-content',
29+
margin: '0 auto 1rem auto',
30+
...style
31+
}}>
32+
<div style={{
33+
fontSize: '0.95rem',
34+
fontWeight: '600',
35+
marginBottom: '0.5rem',
36+
color: '#374151',
37+
textAlign: 'center'
38+
}}>
39+
{title}
40+
</div>
41+
<div style={{
42+
display: 'flex',
43+
gap: '0.75rem',
44+
alignItems: 'center',
45+
justifyContent: 'center'
46+
}}>
47+
{items.map((item, index) => (
48+
<div key={index} style={{
49+
display: 'flex',
50+
alignItems: 'center',
51+
gap: '0.25rem'
52+
}}>
53+
<div style={{
54+
width: '10px',
55+
height: '10px',
56+
backgroundColor: item.color,
57+
borderRadius: '50%'
58+
}} />
59+
<span style={{ fontSize: '0.85rem', color: '#6b7280' }}>
60+
{item.count} {item.label}
61+
</span>
62+
</div>
63+
))}
64+
</div>
65+
</div>
66+
);
67+
};
File renamed without changes.

src/components/Hints/calculations.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,90 @@ export const calculateVaultSegments: HintSegmentCalculator = (
232232
showOnlyNonZero: true,
233233
};
234234
};
235+
236+
/**
237+
* Types for hover content calculations
238+
*/
239+
export interface ResourceTypeStats {
240+
type: string;
241+
total: number;
242+
healthy: number;
243+
creating: number;
244+
unhealthy: number;
245+
healthyPercentage: number;
246+
creatingPercentage: number;
247+
unhealthyPercentage: number;
248+
}
249+
250+
export interface OverallStats {
251+
total: number;
252+
healthy: number;
253+
creating: number;
254+
unhealthy: number;
255+
}
256+
257+
export interface CrossplaneHoverData {
258+
resourceTypeStats: ResourceTypeStats[];
259+
overallStats: OverallStats;
260+
}
261+
262+
/**
263+
* Calculate comprehensive statistics for Crossplane hover content
264+
*/
265+
export const calculateCrossplaneHoverData = (allItems: ManagedResourceItem[]): CrossplaneHoverData => {
266+
const typeStats: Record<string, { total: number; healthy: number; creating: number; unhealthy: number }> = {};
267+
let totalHealthy = 0;
268+
let totalCreating = 0;
269+
let totalUnhealthy = 0;
270+
271+
allItems.forEach((item: ManagedResourceItem) => {
272+
const type = item.kind || 'Unknown';
273+
274+
if (!typeStats[type]) {
275+
typeStats[type] = { total: 0, healthy: 0, creating: 0, unhealthy: 0 };
276+
}
277+
278+
typeStats[type].total++;
279+
280+
const conditions = item.status?.conditions || [];
281+
const ready = conditions.find((c: Condition) => c.type === 'Ready' && c.status === 'True');
282+
const synced = conditions.find((c: Condition) => c.type === 'Synced' && c.status === 'True');
283+
284+
if (ready && synced) {
285+
typeStats[type].healthy++;
286+
totalHealthy++;
287+
} else if (synced && !ready) {
288+
// Resource is synced but not ready - it's creating
289+
typeStats[type].creating++;
290+
totalCreating++;
291+
} else {
292+
// Resource has issues or is not synced
293+
typeStats[type].unhealthy++;
294+
totalUnhealthy++;
295+
}
296+
});
297+
298+
const resourceTypeStats: ResourceTypeStats[] = Object.keys(typeStats).map(type => {
299+
const stats = typeStats[type];
300+
return {
301+
type,
302+
total: stats.total,
303+
healthy: stats.healthy,
304+
creating: stats.creating,
305+
unhealthy: stats.unhealthy,
306+
healthyPercentage: Math.round((stats.healthy / stats.total) * 100),
307+
creatingPercentage: Math.round((stats.creating / stats.total) * 100),
308+
unhealthyPercentage: Math.round((stats.unhealthy / stats.total) * 100),
309+
};
310+
});
311+
312+
return {
313+
resourceTypeStats,
314+
overallStats: {
315+
total: allItems.length,
316+
healthy: totalHealthy,
317+
creating: totalCreating,
318+
unhealthy: totalUnhealthy,
319+
},
320+
};
321+
};

0 commit comments

Comments
 (0)