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:
Shailesh Parmar 2024-09-20 18:47:18 +05:30
parent c75378f8e8
commit 3ba2fd85f1
8 changed files with 98 additions and 4 deletions

View File

@ -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]);

View File

@ -22,4 +22,5 @@ export interface TestSummaryGraphProps {
testCaseResults: TestCaseResult[];
selectedTimeRange: string;
minHeight?: number;
testDefinitionName?: string;
}

View File

@ -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 />}

View File

@ -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>
);

View File

@ -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 () => {

View File

@ -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,

View File

@ -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(

View File

@ -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