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 { 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(() => { | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Harshit Shah
						Harshit Shah