mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-25 17:37:57 +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}
|
testCaseName={data.name}
|
||||||
testCaseParameterValue={data.parameterValues}
|
testCaseParameterValue={data.parameterValues}
|
||||||
testCaseResults={results}
|
testCaseResults={results}
|
||||||
|
testDefinitionName={data.testDefinition.name}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [isGraphLoading, data, results, selectedTimeRange]);
|
}, [isGraphLoading, data, results, selectedTimeRange]);
|
||||||
|
@ -22,4 +22,5 @@ export interface TestSummaryGraphProps {
|
|||||||
testCaseResults: TestCaseResult[];
|
testCaseResults: TestCaseResult[];
|
||||||
selectedTimeRange: string;
|
selectedTimeRange: string;
|
||||||
minHeight?: number;
|
minHeight?: number;
|
||||||
|
testDefinitionName?: string;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,10 @@ import {
|
|||||||
GRAPH_BACKGROUND_COLOR,
|
GRAPH_BACKGROUND_COLOR,
|
||||||
HOVER_CHART_OPACITY,
|
HOVER_CHART_OPACITY,
|
||||||
} from '../../../../constants/constants';
|
} 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 { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum';
|
||||||
import {
|
import {
|
||||||
Thread,
|
Thread,
|
||||||
@ -56,6 +60,7 @@ import {
|
|||||||
axisTickFormatter,
|
axisTickFormatter,
|
||||||
updateActiveChartFilter,
|
updateActiveChartFilter,
|
||||||
} from '../../../../utils/ChartUtils';
|
} from '../../../../utils/ChartUtils';
|
||||||
|
import { formatTimeFromSeconds } from '../../../../utils/CommonUtils';
|
||||||
import { prepareChartData } from '../../../../utils/DataQuality/TestSummaryGraphUtils';
|
import { prepareChartData } from '../../../../utils/DataQuality/TestSummaryGraphUtils';
|
||||||
import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils';
|
import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils';
|
||||||
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
||||||
@ -70,6 +75,7 @@ function TestSummaryGraph({
|
|||||||
testCaseResults,
|
testCaseResults,
|
||||||
selectedTimeRange,
|
selectedTimeRange,
|
||||||
minHeight,
|
minHeight,
|
||||||
|
testDefinitionName,
|
||||||
}: Readonly<TestSummaryGraphProps>) {
|
}: Readonly<TestSummaryGraphProps>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { entityThread = [] } = useActivityFeedProvider();
|
const { entityThread = [] } = useActivityFeedProvider();
|
||||||
@ -92,15 +98,18 @@ function TestSummaryGraph({
|
|||||||
: -200;
|
: -200;
|
||||||
}, [chartRef, chartMouseEvent]);
|
}, [chartRef, chartMouseEvent]);
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
const { chartData, isFreshnessTest } = useMemo(() => {
|
||||||
const data = prepareChartData({
|
const data = prepareChartData({
|
||||||
testCaseParameterValue: testCaseParameterValue ?? [],
|
testCaseParameterValue: testCaseParameterValue ?? [],
|
||||||
testCaseResults,
|
testCaseResults,
|
||||||
entityThread,
|
entityThread,
|
||||||
});
|
});
|
||||||
setShowAILearningBanner(data.showAILearningBanner);
|
setShowAILearningBanner(data.showAILearningBanner);
|
||||||
|
const isFreshnessTest = data.information.some(
|
||||||
|
(value) => value.label === TABLE_FRESHNESS_KEY
|
||||||
|
);
|
||||||
|
|
||||||
return data;
|
return { chartData: data, isFreshnessTest };
|
||||||
}, [testCaseResults, entityThread, testCaseParameterValue]);
|
}, [testCaseResults, entityThread, testCaseParameterValue]);
|
||||||
|
|
||||||
const incidentData = useMemo(() => {
|
const incidentData = useMemo(() => {
|
||||||
@ -164,6 +173,14 @@ function TestSummaryGraph({
|
|||||||
setActiveMouseHoverKey('');
|
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 updatedDot: LineProps['dot'] = (props): ReactElement<SVGElement> => {
|
||||||
const { cx = 0, cy = 0, payload } = props;
|
const { cx = 0, cy = 0, payload } = props;
|
||||||
let fill = payload.status === TestCaseStatus.Success ? GREEN_3 : undefined;
|
let fill = payload.status === TestCaseStatus.Success ? GREEN_3 : undefined;
|
||||||
@ -253,7 +270,8 @@ function TestSummaryGraph({
|
|||||||
allowDataOverflow
|
allowDataOverflow
|
||||||
domain={['min', 'max']}
|
domain={['min', 'max']}
|
||||||
padding={{ top: 8, bottom: 8 }}
|
padding={{ top: 8, bottom: 8 }}
|
||||||
tickFormatter={(value) => axisTickFormatter(value)}
|
tickFormatter={formatYAxis}
|
||||||
|
width={80}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={<TestSummaryCustomTooltip />}
|
content={<TestSummaryCustomTooltip />}
|
||||||
|
@ -16,7 +16,9 @@ import React, { Fragment } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { TooltipProps } from 'recharts';
|
import { TooltipProps } from 'recharts';
|
||||||
|
import { TABLE_FRESHNESS_KEY } from '../../../../constants/TestSuite.constant';
|
||||||
import { Thread } from '../../../../generated/entity/feed/thread';
|
import { Thread } from '../../../../generated/entity/feed/thread';
|
||||||
|
import { formatTimeFromSeconds } from '../../../../utils/CommonUtils';
|
||||||
import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils';
|
import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils';
|
||||||
import { getTaskDetailPath } from '../../../../utils/TasksUtils';
|
import { getTaskDetailPath } from '../../../../utils/TasksUtils';
|
||||||
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
|
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
|
||||||
@ -78,7 +80,10 @@ const TestSummaryCustomTooltip = (
|
|||||||
{startCase(key)}
|
{startCase(key)}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium" data-testid={key}>
|
<span className="font-medium" data-testid={key}>
|
||||||
{value}
|
{/* freshness will always be in seconds */}
|
||||||
|
{key === TABLE_FRESHNESS_KEY && isNumber(value)
|
||||||
|
? formatTimeFromSeconds(value)
|
||||||
|
: value}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -49,6 +49,9 @@ jest.mock('../../../../utils/TasksUtils', () => ({
|
|||||||
jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({
|
jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({
|
||||||
OwnerLabel: jest.fn().mockReturnValue(<div>OwnerLabel</div>),
|
OwnerLabel: jest.fn().mockReturnValue(<div>OwnerLabel</div>),
|
||||||
}));
|
}));
|
||||||
|
jest.mock('../../../../utils/CommonUtils', () => ({
|
||||||
|
formatTimeFromSeconds: jest.fn().mockReturnValue('1 hour'),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Test AddServicePage component', () => {
|
describe('Test AddServicePage component', () => {
|
||||||
it('AddServicePage component should render', async () => {
|
it('AddServicePage component should render', async () => {
|
||||||
|
@ -101,6 +101,8 @@ export const TEST_CASE_STATUS: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TABLE_DIFF = 'tableDiff';
|
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 = [
|
export const SUPPORTED_SERVICES_FOR_TABLE_DIFF = [
|
||||||
DatabaseServiceType.Snowflake,
|
DatabaseServiceType.Snowflake,
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
} from '../generated/type/tagLabel';
|
} from '../generated/type/tagLabel';
|
||||||
import {
|
import {
|
||||||
digitFormatter,
|
digitFormatter,
|
||||||
|
formatTimeFromSeconds,
|
||||||
getBase64EncodedString,
|
getBase64EncodedString,
|
||||||
getIngestionFrequency,
|
getIngestionFrequency,
|
||||||
getIsErrorMatch,
|
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', () => {
|
describe('Tests for sortTagsCaseInsensitive function', () => {
|
||||||
it('GetErrorMessage match function should return true if match found', () => {
|
it('GetErrorMessage match function should return true if match found', () => {
|
||||||
const result = getIsErrorMatch(
|
const result = getIsErrorMatch(
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
toLower,
|
toLower,
|
||||||
toNumber,
|
toNumber,
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
|
import { Duration } from 'luxon';
|
||||||
import {
|
import {
|
||||||
CurrentState,
|
CurrentState,
|
||||||
ExtraInfo,
|
ExtraInfo,
|
||||||
@ -604,6 +605,45 @@ export const digitFormatter = (value: number) => {
|
|||||||
}).format(value);
|
}).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 = (
|
export const getTeamsUser = (
|
||||||
data: ExtraInfo,
|
data: ExtraInfo,
|
||||||
currentUser: User
|
currentUser: User
|
||||||
|
Loading…
x
Reference in New Issue
Block a user