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

View File

@ -547,6 +547,79 @@ describe('TotalDataAssetsWidget', () => {
});
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 () => {
await act(async () => {
renderTotalDataAssetsWidget();