From 507df749cbf7acae510bf809db7062e0f1b2d4ec Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Wed, 25 Jun 2025 18:52:58 +0530 Subject: [PATCH] Refactor Data Quality Summary Panel to use SummaryPieChartCard component - Replaced inline pie chart implementations with a new reusable SummaryPieChartCard component for better code organization and reusability. - Introduced new ChartData and SummaryPieChartCardProps interfaces for type safety. - Updated styles for the new component and adjusted the layout in the DataQuality page. - Added GREY_200 color constant for improved color management in pie charts. - Enhanced percentage calculation utility for better precision and safety in division. --- .../PieChartSummaryPanel.component.tsx | 262 ++++-------- .../SummaryPannel/SummaryPanel.interface.ts | 16 + .../SummaryPieChartCard.component.tsx | 97 +++++ .../summary-pie-chart-card.style.less} | 50 +-- .../TestCases/TestCases.component.tsx | 379 +++++++++--------- .../TestSuiteList/TestSuites.component.tsx | 10 +- .../ui/src/constants/Color.constants.ts | 1 + .../src/pages/DataQuality/DataQualityPage.tsx | 2 +- .../pages/DataQuality/data-quality-page.less | 3 +- .../resources/ui/src/utils/CommonUtils.tsx | 24 ++ 10 files changed, 405 insertions(+), 439 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/SummaryPieChartCard.component.tsx rename openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/{pie-chart-summary-panel.style.less => SummaryPieChartCard/summary-pie-chart-card.style.less} (58%) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/PieChartSummaryPanel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/PieChartSummaryPanel.component.tsx index 1290979e267..4fd84ccad1a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/PieChartSummaryPanel.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/PieChartSummaryPanel.component.tsx @@ -10,24 +10,19 @@ * 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 { Col, Row } from 'antd'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts'; import { BLUE_2, GREEN_3, + GREY_200, RED_3, YELLOW_2, } from '../../../constants/Color.constants'; -import { formatNumberWithComma } from '../../../utils/CommonUtils'; -import './pie-chart-summary-panel.style.less'; +import { calculatePercentage } from '../../../utils/CommonUtils'; import { SummaryPanelProps } from './SummaryPanel.interface'; +import SummaryPieChartCard from './SummaryPieChartCard/SummaryPieChartCard.component'; const PieChartSummaryPanel = ({ testSummary, @@ -45,207 +40,90 @@ const PieChartSummaryPanel = ({ totalEntityCount = 0, } = testSummary || {}; - const totalDataAssets = totalDQEntities; - const dataAssetsCoverage = totalDQEntities; - - const testData = useMemo(() => { - return [ + const testData = useMemo( + () => [ { name: 'Success', value: successTests, color: GREEN_3 }, { name: 'Aborted', value: abortedTests, color: YELLOW_2 }, { name: 'Failed', value: failedTests, color: RED_3 }, - ]; - }, [successTests, abortedTests, failedTests]); + ], + [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; - - return [ + const healthyData = useMemo( + () => [ { name: 'Healthy', value: healthyDataAssets, color: GREEN_3 }, - { name: 'Unhealthy', value: unhealthy, color: '#E5E7EB' }, - ]; - }, [healthyDataAssets, totalDataAssets]); + { + name: 'Unhealthy', + value: totalDQEntities - healthyDataAssets, + color: GREY_200, + }, + ], + [healthyDataAssets, totalDQEntities] + ); - const coverageData = useMemo(() => { - const uncovered = totalEntityCount - dataAssetsCoverage; + const coverageData = useMemo( + () => [ + { name: 'Covered', value: totalDQEntities, color: BLUE_2 }, + { + name: 'Uncovered', + value: totalEntityCount - totalDQEntities, + color: GREY_200, + }, + ], + [totalDQEntities, totalEntityCount] + ); - return [ - { name: 'Covered', value: dataAssetsCoverage, color: BLUE_2 }, - { name: 'Uncovered', value: uncovered, color: '#E5E7EB' }, - ]; - }, [dataAssetsCoverage, totalEntityCount]); + const percentages = useMemo( + () => ({ + testSuccess: calculatePercentage(successTests, totalTests), + healthy: calculatePercentage(healthyDataAssets, totalDQEntities), + coverage: calculatePercentage(totalDQEntities, totalEntityCount), + }), + [ + successTests, + totalTests, + healthyDataAssets, + totalDQEntities, + totalEntityCount, + ] + ); return ( - + - -
-
- -
- - {t('label.total-entity', { - entity: t('label.test-plural'), - })} - - - - {formatNumberWithComma(totalTests)} - -
-
- -
- - {testData.map((item) => ( - -
- - {item.name} {formatNumberWithComma(item.value)} - - - ))} - - - - - {testData.map((entry, index) => ( - - ))} - - - - 100% - - - -
-
- + {showAdditionalSummary && ( <> - -
-
- -
- - {t('label.healthy-data-asset-plural')} - - - - {formatNumberWithComma(healthyDataAssets)} - -
-
-
- - - - {healthyData.map((entry, index) => ( - - ))} - - - - {`${healthyPercentage}%`} - - - -
-
-
+ - -
-
- -
- - {t('label.data-asset-plural-coverage')} - - - - {formatNumberWithComma(dataAssetsCoverage)} - -
-
-
- - - - {coverageData.map((entry, index) => ( - - ))} - - - - {`${coveragePercentage}%`} - - - -
-
-
+ )} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPanel.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPanel.interface.ts index 2c2414e7a8f..8e6b7a4d929 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPanel.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPanel.interface.ts @@ -17,3 +17,19 @@ export interface SummaryPanelProps { isLoading?: boolean; showAdditionalSummary?: boolean; } + +export interface ChartData { + name: string; + value: number; + color: string; +} + +export interface SummaryPieChartCardProps { + title: string; + value: number; + percentage: number; + chartData: ChartData[]; + isLoading?: boolean; + showLegends?: boolean; + paddingAngle?: number; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/SummaryPieChartCard.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/SummaryPieChartCard.component.tsx new file mode 100644 index 00000000000..944009d5134 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/SummaryPieChartCard.component.tsx @@ -0,0 +1,97 @@ +/* + * 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 { Card, Space, Typography } from 'antd'; +import classNames from 'classnames'; +import React from 'react'; +import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts'; +import { formatNumberWithComma } from '../../../../utils/CommonUtils'; +import { SummaryPieChartCardProps } from '../SummaryPanel.interface'; +import './summary-pie-chart-card.style.less'; + +const SummaryPieChartCard = ({ + title, + value, + percentage, + chartData, + isLoading = false, + showLegends = false, + paddingAngle = 0, +}: SummaryPieChartCardProps) => { + return ( + +
+
+ + {title} + + + + {formatNumberWithComma(value)} + +
+ +
+ {showLegends && ( + + {chartData.map((item) => ( + +
+ + {item.name}{' '} + + {formatNumberWithComma(item.value)} + + + + ))} + + )} + + + + + {chartData.map((entry, index) => ( + + ))} + + + + {`${percentage}%`} + + + +
+
+ + ); +}; + +export default SummaryPieChartCard; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/pie-chart-summary-panel.style.less b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/summary-pie-chart-card.style.less similarity index 58% rename from openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/pie-chart-summary-panel.style.less rename to openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/summary-pie-chart-card.style.less index 81fad4a0054..971e2559227 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/pie-chart-summary-panel.style.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/SummaryPannel/SummaryPieChartCard/summary-pie-chart-card.style.less @@ -11,34 +11,9 @@ * limitations under the License. */ -@import (reference) '../../../styles/variables.less'; +@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; @@ -54,28 +29,10 @@ 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; @@ -88,9 +45,4 @@ border-radius: 50%; flex-shrink: 0; } - - .legend-text { - font-size: 12px; - color: @text-grey-muted; - } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx index 056848ddf33..ad0e49cf351 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx @@ -13,7 +13,6 @@ import { RightOutlined } from '@ant-design/icons'; import { Button, - Card, Col, Dropdown, Form, @@ -483,204 +482,202 @@ export const TestCases = () => { } return ( - - - - - form={form} - layout="horizontal" - onValuesChange={handleFilterChange}> - - - handleSearchParam('searchValue', value)} + + + + form={form} + layout="horizontal" + onValuesChange={handleFilterChange}> + + + handleSearchParam('searchValue', value)} + /> + + + + + + + {selectedFilter.includes(TEST_CASE_FILTERS.table) && ( + + - {selectedFilter.includes(TEST_CASE_FILTERS.table) && ( - - - - )} - {selectedFilter.includes(TEST_CASE_FILTERS.type) && ( - - - - )} - {selectedFilter.includes(TEST_CASE_FILTERS.lastRun) && ( - - - - )} - {selectedFilter.includes(TEST_CASE_FILTERS.tags) && ( - - - - )} - {selectedFilter.includes(TEST_CASE_FILTERS.service) && ( - - - - )} - - - - {/* + )} + {selectedFilter.includes(TEST_CASE_FILTERS.type) && ( + + + + )} + {selectedFilter.includes(TEST_CASE_FILTERS.lastRun) && ( + + + + )} + {selectedFilter.includes(TEST_CASE_FILTERS.tags) && ( + + + + )} + {selectedFilter.includes(TEST_CASE_FILTERS.service) && ( + + + + )} + + + + {/* */} - - - - - - - - + + + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx index 91ad7b0fb9d..9900018da3b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx @@ -71,7 +71,7 @@ import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/U import { TableProfilerTab } from '../../../Database/Profiler/ProfilerDashboard/profilerDashboard.interface'; import ProfilerProgressWidget from '../../../Database/Profiler/TableProfiler/ProfilerProgressWidget/ProfilerProgressWidget'; import { TestSuiteSearchParams } from '../../DataQuality.interface'; -import PieChartSummaryPanel from '../../SummaryPannel/PieChartSummaryPanel.component'; +import { SummaryPanel } from '../../SummaryPannel/SummaryPanel.component'; export const TestSuites = () => { const { t } = useTranslation(); @@ -352,15 +352,15 @@ export const TestSuites = () => { - {/* */} - + {/* + /> */} diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Color.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Color.constants.ts index 8e18cf0cf5a..429d5f5d302 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Color.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Color.constants.ts @@ -37,3 +37,4 @@ export const DESERT = '#B56727'; export const PINK_SALMON = '#FF92AE'; export const ELECTRIC_VIOLET = '#9747FF'; export const LEMON_ZEST = '#FFD700'; +export const GREY_200 = '#E9EAEB'; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx index 5fbaaa5338d..b94724f2908 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx @@ -112,7 +112,7 @@ const DataQualityPage = () => { { return (value * percentageValue) / 100; }; + +/** + * Calculates percentage from numerator and denominator with safe division + * @param numerator - The numerator value + * @param denominator - The denominator value + * @param precision - Number of decimal places to round to (default: 1) + * @returns Calculated percentage rounded to specified precision, or 0 if denominator is 0 + * @example + * calculatePercentage(25, 100) // returns 25.0 + * calculatePercentage(1, 3, 2) // returns 33.33 + * calculatePercentage(5, 0) // returns 0 (safe division) + */ +export const calculatePercentage = ( + numerator: number, + denominator: number, + precision = 1 +): number => { + if (denominator === 0) { + return 0; + } + + return round((numerator / denominator) * 100, precision); +};