mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-24 08:58:06 +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