mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 02:29:03 +00:00 
			
		
		
		
	Sort data in total data assets widget by count (#23490)
(cherry picked from commit d8f8d6beb4a39df5021410d4811102a721924ceb)
This commit is contained in:
		
							parent
							
								
									7e6291697f
								
							
						
					
					
						commit
						1cf3649bcc
					
				| @ -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(() => { | ||||||
|  | |||||||
| @ -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(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Harshit Shah
						Harshit Shah