Sort data in total data assets widget by count (#23490)

(cherry picked from commit d8f8d6beb4a39df5021410d4811102a721924ceb)
This commit is contained in:
Harshit Shah 2025-09-22 13:24:31 +05:30 committed by OpenMetadata Release Bot
parent 7e6291697f
commit 1cf3649bcc
2 changed files with 156 additions and 61 deletions

View File

@ -13,7 +13,15 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { groupBy, isEmpty, omit, reduce, sortBy, startCase } from 'lodash'; import {
groupBy,
isEmpty,
omit,
orderBy,
reduce,
sortBy,
startCase,
} from 'lodash';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -75,13 +83,11 @@ const TotalDataAssetsWidget = ({
applicationConfig?.customTheme?.primaryColor ?? applicationConfig?.customTheme?.primaryColor ??
DEFAULT_THEME.primaryColor; DEFAULT_THEME.primaryColor;
// Generate palette and arrange from dark to light for high-to-low data
const fullPalette = generatePalette(primaryColor); const fullPalette = generatePalette(primaryColor);
const reversed = fullPalette.slice().reverse();
const firstTwo = reversed.slice(0, 2); // Reverse the palette to ensure dark-to-light order for high-to-low data
const remaining = reversed.slice(2); return fullPalette.slice().reverse();
return [...remaining, ...firstTwo];
}, [applicationConfig?.customTheme?.primaryColor]); }, [applicationConfig?.customTheme?.primaryColor]);
const widgetData = useMemo(() => { const widgetData = useMemo(() => {
@ -92,15 +98,13 @@ const TotalDataAssetsWidget = ({
return currentLayout?.find((item) => item.i === widgetKey)?.w === 2; return currentLayout?.find((item) => item.i === widgetKey)?.w === 2;
}, [currentLayout, widgetKey]); }, [currentLayout, widgetKey]);
const { graphData, rightSideEntityList, dataByDate, availableDates } = const { graphData, dataByDate, availableDates } = useMemo(() => {
useMemo(() => {
const results = chartData?.results ?? []; const results = chartData?.results ?? [];
const groupedByDay = groupBy(results, 'day'); const groupedByDay = groupBy(results, 'day');
const labels: string[] = []; const labels: string[] = [];
const graphData = Object.entries(groupedByDay).map( const graphData = Object.entries(groupedByDay).map(([dayKey, entries]) => {
([dayKey, entries]) => {
const day = Number(dayKey); const day = Number(dayKey);
const values = entries.reduce((acc, curr) => { const values = entries.reduce((acc, curr) => {
if (curr.group) { if (curr.group) {
@ -118,11 +122,9 @@ const TotalDataAssetsWidget = ({
dayString: customFormatDateTime(day, 'dd MMM'), dayString: customFormatDateTime(day, 'dd MMM'),
...values, ...values,
}; };
} });
);
const sortedData = sortBy(graphData, 'day'); const sortedData = sortBy(graphData, 'day');
const uniqueLabels = Array.from(new Set(labels));
const dataByDate: Record<number, Record<string, number>> = {}; const dataByDate: Record<number, Record<string, number>> = {};
sortedData.forEach((item) => { sortedData.forEach((item) => {
@ -136,23 +138,34 @@ const TotalDataAssetsWidget = ({
return { return {
graphData: sortedData, graphData: sortedData,
rightSideEntityList: uniqueLabels,
dataByDate, dataByDate,
availableDates, availableDates,
}; };
}, [chartData?.results]); }, [chartData?.results]);
const selectedDateData = useMemo(() => { const { selectedDateData, sortedEntityList, totalDatAssets } = useMemo(() => {
if (!selectedDate) { if (!selectedDate) {
return {}; return { selectedDateData: {}, sortedEntityList: [], totalDatAssets: 0 };
} }
return dataByDate[selectedDate] ?? {}; const rawData = dataByDate[selectedDate] ?? {};
}, [selectedDate, dataByDate]);
const totalDatAssets = useMemo(() => { // Sort data by count (high to low) and create sorted structures
return reduce(selectedDateData, (acc, value) => acc + value, 0); const sortedEntries = orderBy(
}, [selectedDateData]); Object.entries(rawData),
([, value]) => value,
'desc'
);
const sortedData = Object.fromEntries(sortedEntries);
const entityList = Object.keys(sortedData);
const total = reduce(sortedData, (acc, value) => acc + value, 0);
return {
selectedDateData: sortedData,
sortedEntityList: entityList,
totalDatAssets: total,
};
}, [selectedDate, dataByDate]);
const fetchData = async () => { const fetchData = async () => {
setIsLoading(true); setIsLoading(true);
@ -222,7 +235,7 @@ const TotalDataAssetsWidget = ({
nameKey="name" nameKey="name"
outerRadius={117} outerRadius={117}
paddingAngle={1}> paddingAngle={1}>
{rightSideEntityList.map((label, index) => ( {sortedEntityList.map((label, index) => (
<Cell <Cell
fill={pieChartColors[index % pieChartColors.length]} fill={pieChartColors[index % pieChartColors.length]}
key={label} key={label}
@ -252,11 +265,17 @@ const TotalDataAssetsWidget = ({
{/* Right-side Legend */} {/* Right-side Legend */}
{isFullSizeWidget && ( {isFullSizeWidget && (
<div className="flex-1 legend-list p-md"> <div
{rightSideEntityList.map((label, index) => ( className="flex-1 legend-list p-md"
<div className="d-flex items-center gap-3 text-sm" key={label}> data-testid="assets-legend">
{sortedEntityList.map((label, index) => (
<div
className="d-flex items-center gap-3 text-sm"
data-testid={`legend-item-${label}`}
key={label}>
<span <span
className="h-3 w-3" className="h-3 w-3"
data-testid={`legend-color-${label}`}
style={{ style={{
borderRadius: '50%', borderRadius: '50%',
backgroundColor: backgroundColor:
@ -266,7 +285,9 @@ const TotalDataAssetsWidget = ({
<Typography.Text ellipsis={{ tooltip: true }}> <Typography.Text ellipsis={{ tooltip: true }}>
{startCase(label)} {startCase(label)}
</Typography.Text> </Typography.Text>
<span className="text-xs font-medium p-y-xss p-x-xs data-value"> <span
className="text-xs font-medium p-y-xss p-x-xs data-value"
data-testid={`legend-count-${label}`}>
{selectedDateData[label] ?? 0} {selectedDateData[label] ?? 0}
</span> </span>
</div> </div>
@ -298,8 +319,9 @@ const TotalDataAssetsWidget = ({
selectedDate, selectedDate,
selectedDateData, selectedDateData,
totalDatAssets, totalDatAssets,
rightSideEntityList, sortedEntityList,
isFullSizeWidget, isFullSizeWidget,
pieChartColors,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -547,6 +547,79 @@ describe('TotalDataAssetsWidget', () => {
}); });
describe('Data Processing', () => { describe('Data Processing', () => {
it('should sort data by count in descending order', async () => {
const sortedTestData: DataInsightCustomChartResult = {
results: [
{
count: 50,
day: 1640995200000,
group: 'dashboard',
term: 'dashboard',
},
{
count: 200,
day: 1640995200000,
group: 'table',
term: 'table',
},
{
count: 100,
day: 1640995200000,
group: 'topic',
term: 'topic',
},
],
};
(getChartPreviewByName as jest.Mock).mockResolvedValueOnce(
sortedTestData
);
await act(async () => {
renderTotalDataAssetsWidget();
});
await waitFor(() => {
expect(screen.getByText('350')).toBeInTheDocument(); // Total count
});
// Verify legend items are sorted by count (high to low)
await waitFor(() => {
// Verify specific legend items exist with correct counts using data-testid
expect(screen.getByTestId('legend-count-table')).toHaveTextContent(
'200'
);
expect(screen.getByTestId('legend-count-topic')).toHaveTextContent(
'100'
);
expect(screen.getByTestId('legend-count-dashboard')).toHaveTextContent(
'50'
);
// Verify legend items appear in sorted order (highest count first)
const legendContainer = screen.getByTestId('assets-legend');
const legendItems = legendContainer.querySelectorAll(
'[data-testid^="legend-item-"]'
);
// First item should be table (highest count)
expect(legendItems[0]).toHaveAttribute(
'data-testid',
'legend-item-table'
);
// Second item should be topic (medium count)
expect(legendItems[1]).toHaveAttribute(
'data-testid',
'legend-item-topic'
);
// Third item should be dashboard (lowest count)
expect(legendItems[2]).toHaveAttribute(
'data-testid',
'legend-item-dashboard'
);
});
});
it('should correctly group data by date', async () => { it('should correctly group data by date', async () => {
await act(async () => { await act(async () => {
renderTotalDataAssetsWidget(); renderTotalDataAssetsWidget();