mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 18:48:35 +00:00 
			
		
		
		
	Minor: added seconds to human-readable format scale for test case graph (#17926)
* Minor: added milliseconds to human-readable format scale for test case graph * addressing comment * fixed unit test * addressing comment
This commit is contained in:
		
							parent
							
								
									c75378f8e8
								
							
						
					
					
						commit
						3ba2fd85f1
					
				| @ -91,6 +91,7 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => { | ||||
|         testCaseName={data.name} | ||||
|         testCaseParameterValue={data.parameterValues} | ||||
|         testCaseResults={results} | ||||
|         testDefinitionName={data.testDefinition.name} | ||||
|       /> | ||||
|     ); | ||||
|   }, [isGraphLoading, data, results, selectedTimeRange]); | ||||
|  | ||||
| @ -22,4 +22,5 @@ export interface TestSummaryGraphProps { | ||||
|   testCaseResults: TestCaseResult[]; | ||||
|   selectedTimeRange: string; | ||||
|   minHeight?: number; | ||||
|   testDefinitionName?: string; | ||||
| } | ||||
|  | ||||
| @ -45,6 +45,10 @@ import { | ||||
|   GRAPH_BACKGROUND_COLOR, | ||||
|   HOVER_CHART_OPACITY, | ||||
| } from '../../../../constants/constants'; | ||||
| import { | ||||
|   TABLE_DATA_TO_BE_FRESH, | ||||
|   TABLE_FRESHNESS_KEY, | ||||
| } from '../../../../constants/TestSuite.constant'; | ||||
| import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum'; | ||||
| import { | ||||
|   Thread, | ||||
| @ -56,6 +60,7 @@ import { | ||||
|   axisTickFormatter, | ||||
|   updateActiveChartFilter, | ||||
| } from '../../../../utils/ChartUtils'; | ||||
| import { formatTimeFromSeconds } from '../../../../utils/CommonUtils'; | ||||
| import { prepareChartData } from '../../../../utils/DataQuality/TestSummaryGraphUtils'; | ||||
| import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils'; | ||||
| import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider'; | ||||
| @ -70,6 +75,7 @@ function TestSummaryGraph({ | ||||
|   testCaseResults, | ||||
|   selectedTimeRange, | ||||
|   minHeight, | ||||
|   testDefinitionName, | ||||
| }: Readonly<TestSummaryGraphProps>) { | ||||
|   const { t } = useTranslation(); | ||||
|   const { entityThread = [] } = useActivityFeedProvider(); | ||||
| @ -92,15 +98,18 @@ function TestSummaryGraph({ | ||||
|       : -200; | ||||
|   }, [chartRef, chartMouseEvent]); | ||||
| 
 | ||||
|   const chartData = useMemo(() => { | ||||
|   const { chartData, isFreshnessTest } = useMemo(() => { | ||||
|     const data = prepareChartData({ | ||||
|       testCaseParameterValue: testCaseParameterValue ?? [], | ||||
|       testCaseResults, | ||||
|       entityThread, | ||||
|     }); | ||||
|     setShowAILearningBanner(data.showAILearningBanner); | ||||
|     const isFreshnessTest = data.information.some( | ||||
|       (value) => value.label === TABLE_FRESHNESS_KEY | ||||
|     ); | ||||
| 
 | ||||
|     return data; | ||||
|     return { chartData: data, isFreshnessTest }; | ||||
|   }, [testCaseResults, entityThread, testCaseParameterValue]); | ||||
| 
 | ||||
|   const incidentData = useMemo(() => { | ||||
| @ -164,6 +173,14 @@ function TestSummaryGraph({ | ||||
|     setActiveMouseHoverKey(''); | ||||
|   }; | ||||
| 
 | ||||
|   // Todo: need to find better approach to create dynamic scale for graph, need to work with @TeddyCr for the same!
 | ||||
|   const formatYAxis = (value: number) => { | ||||
|     // table freshness will always have output value in seconds
 | ||||
|     return testDefinitionName === TABLE_DATA_TO_BE_FRESH || isFreshnessTest | ||||
|       ? formatTimeFromSeconds(value) | ||||
|       : axisTickFormatter(value); | ||||
|   }; | ||||
| 
 | ||||
|   const updatedDot: LineProps['dot'] = (props): ReactElement<SVGElement> => { | ||||
|     const { cx = 0, cy = 0, payload } = props; | ||||
|     let fill = payload.status === TestCaseStatus.Success ? GREEN_3 : undefined; | ||||
| @ -253,7 +270,8 @@ function TestSummaryGraph({ | ||||
|           allowDataOverflow | ||||
|           domain={['min', 'max']} | ||||
|           padding={{ top: 8, bottom: 8 }} | ||||
|           tickFormatter={(value) => axisTickFormatter(value)} | ||||
|           tickFormatter={formatYAxis} | ||||
|           width={80} | ||||
|         /> | ||||
|         <Tooltip | ||||
|           content={<TestSummaryCustomTooltip />} | ||||
|  | ||||
| @ -16,7 +16,9 @@ import React, { Fragment } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { TooltipProps } from 'recharts'; | ||||
| import { TABLE_FRESHNESS_KEY } from '../../../../constants/TestSuite.constant'; | ||||
| import { Thread } from '../../../../generated/entity/feed/thread'; | ||||
| import { formatTimeFromSeconds } from '../../../../utils/CommonUtils'; | ||||
| import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils'; | ||||
| import { getTaskDetailPath } from '../../../../utils/TasksUtils'; | ||||
| import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component'; | ||||
| @ -78,7 +80,10 @@ const TestSummaryCustomTooltip = ( | ||||
|           {startCase(key)} | ||||
|         </span> | ||||
|         <span className="font-medium" data-testid={key}> | ||||
|           {value} | ||||
|           {/* freshness will always be in seconds  */} | ||||
|           {key === TABLE_FRESHNESS_KEY && isNumber(value) | ||||
|             ? formatTimeFromSeconds(value) | ||||
|             : value} | ||||
|         </span> | ||||
|       </li> | ||||
|     ); | ||||
|  | ||||
| @ -49,6 +49,9 @@ jest.mock('../../../../utils/TasksUtils', () => ({ | ||||
| jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({ | ||||
|   OwnerLabel: jest.fn().mockReturnValue(<div>OwnerLabel</div>), | ||||
| })); | ||||
| jest.mock('../../../../utils/CommonUtils', () => ({ | ||||
|   formatTimeFromSeconds: jest.fn().mockReturnValue('1 hour'), | ||||
| })); | ||||
| 
 | ||||
| describe('Test AddServicePage component', () => { | ||||
|   it('AddServicePage component should render', async () => { | ||||
|  | ||||
| @ -101,6 +101,8 @@ export const TEST_CASE_STATUS: Record< | ||||
| }; | ||||
| 
 | ||||
| export const TABLE_DIFF = 'tableDiff'; | ||||
| export const TABLE_DATA_TO_BE_FRESH = 'tableDataToBeFresh'; | ||||
| export const TABLE_FRESHNESS_KEY = 'freshness'; | ||||
| 
 | ||||
| export const SUPPORTED_SERVICES_FOR_TABLE_DIFF = [ | ||||
|   DatabaseServiceType.Snowflake, | ||||
|  | ||||
| @ -27,6 +27,7 @@ import { | ||||
| } from '../generated/type/tagLabel'; | ||||
| import { | ||||
|   digitFormatter, | ||||
|   formatTimeFromSeconds, | ||||
|   getBase64EncodedString, | ||||
|   getIngestionFrequency, | ||||
|   getIsErrorMatch, | ||||
| @ -137,6 +138,29 @@ describe('Tests for CommonUtils', () => { | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     // formatTimeFromSeconds test
 | ||||
|     it('formatTimeFromSeconds formatter should format mills to human readable value', () => { | ||||
|       const values = [ | ||||
|         { input: 1, expected: '1 second' }, | ||||
|         { input: 2, expected: '2 seconds' }, | ||||
|         { input: 30, expected: '30 seconds' }, | ||||
|         { input: 60, expected: '1 minute' }, | ||||
|         { input: 120, expected: '2 minutes' }, | ||||
|         { input: 3600, expected: '1 hour' }, | ||||
|         { input: 7200, expected: '2 hours' }, | ||||
|         { input: 86400, expected: '1 day' }, | ||||
|         { input: 172800, expected: '2 days' }, | ||||
|         { input: 2592000, expected: '1 month' }, | ||||
|         { input: 5184000, expected: '2 months' }, | ||||
|         { input: 31536000, expected: '1 year' }, | ||||
|         { input: 63072000, expected: '2 years' }, | ||||
|       ]; | ||||
| 
 | ||||
|       values.map(({ input, expected }) => { | ||||
|         expect(formatTimeFromSeconds(input)).toEqual(expected); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe('Tests for sortTagsCaseInsensitive function', () => { | ||||
|       it('GetErrorMessage match function should return true if match found', () => { | ||||
|         const result = getIsErrorMatch( | ||||
|  | ||||
| @ -26,6 +26,7 @@ import { | ||||
|   toLower, | ||||
|   toNumber, | ||||
| } from 'lodash'; | ||||
| import { Duration } from 'luxon'; | ||||
| import { | ||||
|   CurrentState, | ||||
|   ExtraInfo, | ||||
| @ -604,6 +605,45 @@ export const digitFormatter = (value: number) => { | ||||
|   }).format(value); | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Converts a duration in seconds to a human-readable format. | ||||
|  * The function returns the largest time unit (years, months, days, hours, minutes, or seconds) | ||||
|  * that is greater than or equal to one, rounded to the nearest whole number. | ||||
|  * | ||||
|  * @param {number} seconds - The duration in seconds to be converted. | ||||
|  * @returns {string} A string representing the duration in a human-readable format, | ||||
|  *                  e.g., "1 hour", "2 days", "3 months", etc. | ||||
|  * | ||||
|  * @example | ||||
|  * formatTimeFromSeconds(1); // returns "1 second"
 | ||||
|  * formatTimeFromSeconds(60); // returns "1 minute"
 | ||||
|  * formatTimeFromSeconds(3600); // returns "1 hour"
 | ||||
|  * formatTimeFromSeconds(86400); // returns "1 day"
 | ||||
|  */ | ||||
| export const formatTimeFromSeconds = (seconds: number): string => { | ||||
|   const duration = Duration.fromObject({ seconds }); | ||||
|   let unit: keyof Duration; | ||||
| 
 | ||||
|   if (duration.as('years') >= 1) { | ||||
|     unit = 'years'; | ||||
|   } else if (duration.as('months') >= 1) { | ||||
|     unit = 'months'; | ||||
|   } else if (duration.as('days') >= 1) { | ||||
|     unit = 'days'; | ||||
|   } else if (duration.as('hours') >= 1) { | ||||
|     unit = 'hours'; | ||||
|   } else if (duration.as('minutes') >= 1) { | ||||
|     unit = 'minutes'; | ||||
|   } else { | ||||
|     unit = 'seconds'; | ||||
|   } | ||||
| 
 | ||||
|   const value = Math.round(duration.as(unit)); | ||||
|   const unitSingular = unit.slice(0, -1); | ||||
| 
 | ||||
|   return `${value} ${value === 1 ? unitSingular : unit}`; | ||||
| }; | ||||
| 
 | ||||
| export const getTeamsUser = ( | ||||
|   data: ExtraInfo, | ||||
|   currentUser: User | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Shailesh Parmar
						Shailesh Parmar