mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-27 18:05:00 +00:00
updated pie chart with new mock
This commit is contained in:
parent
c6682384d9
commit
3ff41d3438
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user