updated pie chart with new mock

This commit is contained in:
Shailesh Parmar 2025-06-24 21:44:27 +05:30
parent c6682384d9
commit 3ff41d3438
2 changed files with 328 additions and 222 deletions

View File

@ -1,245 +1,255 @@
import { Card, Col, Row, Typography } from 'antd'; /*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
CheckCircleOutlined,
FilterOutlined,
SnippetsOutlined,
} from '@ant-design/icons';
import { Card, Col, Row, Space, Typography } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Cell, Pie, PieChart } from 'recharts'; import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
import { PRIMARY_COLOR, WHITE_SMOKE } from '../../../constants/Color.constants'; import {
import PageHeader from '../../PageHeader/PageHeader.component'; BLUE_2,
GREEN_3,
RED_3,
YELLOW_2,
} from '../../../constants/Color.constants';
import { formatNumberWithComma } from '../../../utils/CommonUtils';
import './pie-chart-summary-panel.style.less';
import { SummaryPanelProps } from './SummaryPanel.interface'; import { SummaryPanelProps } from './SummaryPanel.interface';
const renderCenterText = (value: number) => (
<text
dominantBaseline="middle"
fill="#222"
fontSize="28"
fontWeight="bold"
textAnchor="middle"
x="50%"
y="50%">
{value.toLocaleString()}
</text>
);
const PieChartSummaryPanel = ({ const PieChartSummaryPanel = ({
testSummary, testSummary,
isLoading = false, isLoading = false,
showAdditionalSummary = true,
}: SummaryPanelProps) => { }: SummaryPanelProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const {
total: totalTests = 0,
success: successTests = 0,
failed: failedTests = 0,
aborted: abortedTests = 0,
healthy: healthyDataAssets = 0,
totalDQEntities = 0,
totalEntityCount = 0,
} = testSummary || {};
const healthyDataAssets = useMemo(() => { const totalDataAssets = totalDQEntities;
return [ const dataAssetsCoverage = totalDQEntities;
{
name: 'healthy',
value: testSummary?.healthy,
color: '#12B76A',
},
{
name: 'total',
value:
(testSummary?.totalDQEntities ?? 0) - (testSummary?.healthy ?? 0),
color: WHITE_SMOKE,
},
];
}, [testSummary?.healthy, testSummary?.totalDQEntities]);
const dataAssetsCoverage = useMemo(() => { const testData = useMemo(() => {
return [ return [
{ { name: 'Success', value: successTests, color: GREEN_3 },
name: 'dataAssetsCoverage', { name: 'Aborted', value: abortedTests, color: YELLOW_2 },
value: testSummary?.totalDQEntities, { name: 'Failed', value: failedTests, color: RED_3 },
color: PRIMARY_COLOR,
},
{
name: 'total',
value:
(testSummary?.totalEntityCount ?? 0) -
(testSummary?.totalDQEntities ?? 0),
color: WHITE_SMOKE,
},
]; ];
}, [testSummary?.totalEntityCount, testSummary?.totalDQEntities]); }, [successTests, abortedTests, failedTests]);
const healthyPercentage = useMemo(() => {
if (totalDataAssets === 0) {
return 0;
}
return Math.round((healthyDataAssets / totalDataAssets) * 100);
}, [healthyDataAssets, totalDataAssets]);
const coveragePercentage = useMemo(() => {
if (totalEntityCount === 0) {
return 0;
}
return Math.round((dataAssetsCoverage / totalEntityCount) * 100);
}, [dataAssetsCoverage, totalEntityCount]);
const healthyData = useMemo(() => {
const unhealthy = totalDataAssets - healthyDataAssets;
const testCaseSummary = useMemo(() => {
return [ return [
{ { name: 'Healthy', value: healthyDataAssets, color: GREEN_3 },
name: 'success', { name: 'Unhealthy', value: unhealthy, color: '#E5E7EB' },
data: [
{
name: 'success',
value: testSummary?.success,
color: '#12B76A',
},
{
name: 'total',
value: (testSummary?.total ?? 0) - (testSummary?.success ?? 0),
color: WHITE_SMOKE,
},
],
innerRadius: 90,
outerRadius: 100,
},
{
name: 'failed',
data: [
{
name: 'failed',
value: testSummary?.failed,
color: '#EF4444',
},
{
name: 'total',
value: (testSummary?.total ?? 0) - (testSummary?.failed ?? 0),
color: WHITE_SMOKE,
},
],
innerRadius: 70,
outerRadius: 80,
},
{
name: 'aborted',
data: [
{
name: 'aborted',
value: testSummary?.aborted,
color: '#F59E0B',
},
{
name: 'total',
value: (testSummary?.total ?? 0) - (testSummary?.aborted ?? 0),
color: WHITE_SMOKE,
},
],
innerRadius: 50,
outerRadius: 60,
},
]; ];
}, [testSummary]); }, [healthyDataAssets, totalDataAssets]);
const coverageData = useMemo(() => {
const uncovered = totalEntityCount - dataAssetsCoverage;
return [
{ name: 'Covered', value: dataAssetsCoverage, color: BLUE_2 },
{ name: 'Uncovered', value: uncovered, color: '#E5E7EB' },
];
}, [dataAssetsCoverage, totalEntityCount]);
return ( return (
<Card loading={isLoading}> <Row className="pie-chart-summary-panel" gutter={[16, 16]}>
<PageHeader <Col md={8} sm={24} xs={24}>
data={{ <Card className="h-full" loading={isLoading}>
header: t('label.data-quality'), <div className="d-flex justify-between items-center">
subHeader: t('message.page-sub-header-for-data-quality'), <div className="d-flex items-center gap-4">
}} <FilterOutlined className="summary-icon total-tests-icon" />
/> <div>
<Row className="p-t-box" gutter={[32, 16]} justify="center"> <Typography.Paragraph className="summary-title">
{/* Total Tests */} {t('label.total-entity', {
<Col md={8} xs={24}> entity: t('label.test-plural'),
<Card style={{ textAlign: 'center', minHeight: 300 }}> })}
<Typography.Title </Typography.Paragraph>
className="heading"
data-testid="heading" <Typography.Paragraph className="summary-value m-b-0">
level={5}> {formatNumberWithComma(totalTests)}
{t('label.total-entity', { </Typography.Paragraph>
entity: t('label.test-case-plural'), </div>
})}
</Typography.Title>
<div className="flex-center m-y-md" style={{ height: 200 }}>
<PieChart height={200} width={200}>
{testCaseSummary.map((item) => {
return (
<Pie
cx="50%"
cy="50%"
data={item.data}
dataKey="value"
endAngle={-270}
innerRadius={item.innerRadius}
key={item.name}
outerRadius={item.outerRadius}
startAngle={90}>
{item.data?.map((entry) => (
<Cell fill={entry.color} key={`cell-${entry.name}`} />
))}
</Pie>
);
})}
{renderCenterText(testSummary?.total || 0)}
</PieChart>
</div> </div>
<div className="flex-center gap-6">
{testCaseSummary?.map((item) => ( <div className="chart-container d-flex items-center">
<div className="flex-center gap-2" key={item.name}> <Space className="m-r-md" direction="vertical" size={4}>
<span {testData.map((item) => (
style={{ <Space key={item.name} size={8}>
width: 10, <div
height: 10, className="legend-dot"
borderRadius: '50%', style={{ backgroundColor: item.color }}
background: item.data[0].color, />
display: 'inline-block', <Typography.Text className="legend-text">
}} {item.name} {formatNumberWithComma(item.value)}
/> </Typography.Text>
<span style={{ color: '#222', fontWeight: 500 }}> </Space>
{t(item.name)} ))}
</span> </Space>
<span style={{ color: '#6B7280', fontWeight: 500 }}> <ResponsiveContainer height={120} width={120}>
{item.data[0].value} <PieChart>
</span> <Pie
cx="50%"
cy="50%"
data={testData}
dataKey="value"
innerRadius={35}
outerRadius={55}
paddingAngle={2}>
{testData.map((entry, index) => (
<Cell fill={entry.color} key={`cell-${index}`} />
))}
</Pie>
<Tooltip />
<text
className="chart-center-text"
dominantBaseline="middle"
textAnchor="middle"
x="50%"
y="50%">
100%
</text>
</PieChart>
</ResponsiveContainer>
</div>
</div>
</Card>
</Col>
{showAdditionalSummary && (
<>
<Col md={8} sm={24} xs={24}>
<Card className="h-full" loading={isLoading}>
<div className="d-flex justify-between items-center">
<div className="d-flex items-center gap-4">
<CheckCircleOutlined className="summary-icon healthy-icon" />
<div>
<Typography.Paragraph className="summary-title">
{t('label.healthy-data-asset-plural')}
</Typography.Paragraph>
<Typography.Paragraph className="summary-value m-b-0">
{formatNumberWithComma(healthyDataAssets)}
</Typography.Paragraph>
</div>
</div> </div>
))} <div className="chart-container">
</div> <ResponsiveContainer height={120} width={120}>
</Card> <PieChart>
</Col> <Pie
{/* Healthy Data Assets */} cx="50%"
<Col md={8} xs={24}> cy="50%"
<Card style={{ textAlign: 'center', minHeight: 300 }}> data={healthyData}
<Typography.Title dataKey="value"
className="heading" innerRadius={35}
data-testid="heading" outerRadius={55}
level={5}> paddingAngle={0}>
{t('label.healthy-data-asset-plural')} {healthyData.map((entry, index) => (
</Typography.Title> <Cell fill={entry.color} key={`cell-${index}`} />
<div className="flex-center m-y-md" style={{ height: 200 }}> ))}
<PieChart height={200} width={200}> </Pie>
<Pie <Tooltip />
cx="50%" <text
cy="50%" className="chart-center-text"
data={healthyDataAssets} dominantBaseline="middle"
dataKey="value" textAnchor="middle"
endAngle={-270} x="50%"
innerRadius={90} y="50%">
outerRadius={100} {`${healthyPercentage}%`}
startAngle={90}> </text>
{healthyDataAssets?.map((entry, idx) => ( </PieChart>
<Cell fill={entry.color} key={`cell-healthy-${idx}`} /> </ResponsiveContainer>
))} </div>
</Pie> </div>
{renderCenterText(testSummary?.healthy || 0)} </Card>
</PieChart> </Col>
</div>
</Card> <Col md={8} sm={24} xs={24}>
</Col> <Card className="h-full" loading={isLoading}>
{/* Data Assets Coverage */} <div className="d-flex justify-between items-center">
<Col md={8} xs={24}> <div className="d-flex items-center gap-4">
<Card style={{ textAlign: 'center', minHeight: 300 }}> <SnippetsOutlined className="summary-icon coverage-icon" />
<Typography.Title <div>
className="heading" <Typography.Paragraph className="summary-title">
data-testid="heading" {t('label.data-asset-plural-coverage')}
level={5}> </Typography.Paragraph>
{t('label.data-asset-plural-coverage')}
</Typography.Title> <Typography.Paragraph className="summary-value m-b-0">
<div className="flex-center m-y-md" style={{ height: 200 }}> {formatNumberWithComma(dataAssetsCoverage)}
<PieChart height={200} width={200}> </Typography.Paragraph>
<Pie </div>
cx="50%" </div>
cy="50%" <div className="chart-container">
data={dataAssetsCoverage} <ResponsiveContainer height={120} width={120}>
dataKey="value" <PieChart>
endAngle={-270} <Pie
innerRadius={90} cx="50%"
outerRadius={100} cy="50%"
startAngle={90}> data={coverageData}
{dataAssetsCoverage?.map((entry, idx) => ( dataKey="value"
<Cell fill={entry.color} key={`cell-coverage-${idx}`} /> innerRadius={35}
))} outerRadius={55}
</Pie> paddingAngle={0}>
{renderCenterText(testSummary?.totalDQEntities || 0)} {coverageData.map((entry, index) => (
</PieChart> <Cell fill={entry.color} key={`cell-${index}`} />
</div> ))}
</Card> </Pie>
</Col> <Tooltip />
</Row> <text
</Card> className="chart-center-text"
dominantBaseline="middle"
textAnchor="middle"
x="50%"
y="50%">
{`${coveragePercentage}%`}
</text>
</PieChart>
</ResponsiveContainer>
</div>
</div>
</Card>
</Col>
</>
)}
</Row>
); );
}; };

View File

@ -0,0 +1,96 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import (reference) '../../../styles/variables.less';
.pie-chart-summary-panel {
.summary-icon {
font-size: 28px;
&.total-tests-icon {
color: #3b82f6;
background: #eff6ff;
padding: 8px;
border-radius: 6px;
}
&.healthy-icon {
color: @green-3;
background: #f0fdf4;
padding: 8px;
border-radius: 6px;
}
&.coverage-icon {
color: @blue-2;
background: #e0f2fe;
padding: 8px;
border-radius: 6px;
}
}
.summary-title {
font-size: 16px;
color: @text-color;
font-weight: 600;
margin-bottom: 0;
}
.summary-value {
font-size: 30px;
font-weight: 600;
color: @text-color;
line-height: 1.2;
margin-top: 8px;
}
.percentage-change {
font-size: 12px;
font-weight: 500;
&.positive {
color: @green-3;
}
&.negative {
color: @red-3;
}
}
.chart-container {
position: relative;
}
.title-value-section {
flex-shrink: 0;
margin-right: auto;
}
.chart-center-text {
font-size: 16px;
font-weight: 600;
fill: @text-color;
}
.legend-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.legend-text {
font-size: 12px;
color: @text-grey-muted;
}
}