mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-03 12:08:31 +00:00
improvement: added count support for test case and pipeline tab. (#20279)
This commit is contained in:
parent
4edce20955
commit
56df47d623
@ -23,7 +23,7 @@ import {
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ReactComponent as SettingIcon } from '../../../../../assets/svg/ic-settings-primery.svg';
|
||||
@ -41,10 +41,12 @@ import { INITIAL_TEST_SUMMARY } from '../../../../../constants/TestSuite.constan
|
||||
import { useLimitStore } from '../../../../../context/LimitsProvider/useLimitsStore';
|
||||
import { EntityTabs, EntityType } from '../../../../../enums/entity.enum';
|
||||
import { ProfilerDashboardType } from '../../../../../enums/table.enum';
|
||||
import { PipelineType } from '../../../../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { TestCaseStatus } from '../../../../../generated/tests/testCase';
|
||||
import LimitWrapper from '../../../../../hoc/LimitWrapper';
|
||||
import useCustomLocation from '../../../../../hooks/useCustomLocation/useCustomLocation';
|
||||
import { useFqn } from '../../../../../hooks/useFqn';
|
||||
import { getIngestionPipelines } from '../../../../../rest/ingestionPipelineAPI';
|
||||
import {
|
||||
ListTestCaseParamsBySearch,
|
||||
TestCaseType,
|
||||
@ -107,6 +109,28 @@ export const QualityTab = () => {
|
||||
const [sortOptions, setSortOptions] =
|
||||
useState<ListTestCaseParamsBySearch>(DEFAULT_SORT_ORDER);
|
||||
const testSuite = useMemo(() => table?.testSuite, [table]);
|
||||
const [ingestionPipelineCount, setIngestionPipelineCount] =
|
||||
useState<number>(0);
|
||||
|
||||
const fetchIngestionPipelineCount = async () => {
|
||||
try {
|
||||
const { paging: ingestionPipelinePaging } = await getIngestionPipelines({
|
||||
arrQueryFields: [],
|
||||
testSuite: testSuite?.fullyQualifiedName ?? '',
|
||||
pipelineType: [PipelineType.TestSuite],
|
||||
limit: 0,
|
||||
});
|
||||
setIngestionPipelineCount(ingestionPipelinePaging.total);
|
||||
} catch (error) {
|
||||
// do nothing for count error
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (testSuite?.fullyQualifiedName) {
|
||||
fetchIngestionPipelineCount();
|
||||
}
|
||||
}, [testSuite?.fullyQualifiedName]);
|
||||
|
||||
const handleTestCasePageChange: NextPreviousProps['pagingHandler'] = ({
|
||||
currentPage,
|
||||
@ -161,7 +185,13 @@ export const QualityTab = () => {
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('label.test-case-plural'),
|
||||
label: (
|
||||
<TabsLabel
|
||||
count={paging.total}
|
||||
id={EntityTabs.TEST_CASES}
|
||||
name={t('label.test-case-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.TEST_CASES,
|
||||
children: (
|
||||
<Row className="p-t-md">
|
||||
@ -208,7 +238,13 @@ export const QualityTab = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
label: t('label.pipeline'),
|
||||
label: (
|
||||
<TabsLabel
|
||||
count={ingestionPipelineCount}
|
||||
id={EntityTabs.PIPELINE}
|
||||
name={t('label.pipeline-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.PIPELINE,
|
||||
children: <TestSuitePipelineTab testSuite={testSuite} />,
|
||||
},
|
||||
@ -222,6 +258,7 @@ export const QualityTab = () => {
|
||||
getResourceLimit,
|
||||
tableBreadcrumb,
|
||||
testCasePaging,
|
||||
ingestionPipelineCount,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import LimitWrapper from '../../../../../hoc/LimitWrapper';
|
||||
import { MOCK_TABLE } from '../../../../../mocks/TableData.mock';
|
||||
import { getIngestionPipelines } from '../../../../../rest/ingestionPipelineAPI';
|
||||
import { useTableProfiler } from '../TableProfilerProvider';
|
||||
import { QualityTab } from './QualityTab.component';
|
||||
|
||||
@ -129,6 +130,21 @@ jest.mock('../../../../../hooks/useCustomLocation/useCustomLocation', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../common/TabsLabel/TabsLabel.component', () => {
|
||||
return jest.fn().mockImplementation(({ id, name, count = 0 }) => (
|
||||
<div data-testid={id}>
|
||||
<div>{name}</div>
|
||||
<span data-testid={`${id}-count`}>{count}</span>
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../rest/ingestionPipelineAPI', () => ({
|
||||
getIngestionPipelines: jest.fn().mockResolvedValue({
|
||||
paging: { total: 0 },
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('QualityTab', () => {
|
||||
it('should render QualityTab', async () => {
|
||||
await act(async () => {
|
||||
@ -157,7 +173,10 @@ describe('QualityTab', () => {
|
||||
expect(
|
||||
await screen.findByText('DataQualityTab.component')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('label.pipeline')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('label.pipeline-plural')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('pipeline-count')).toHaveTextContent('0');
|
||||
});
|
||||
|
||||
it("Pagination should be called with 'handlePageChange'", async () => {
|
||||
@ -248,12 +267,10 @@ describe('QualityTab', () => {
|
||||
render(<QualityTab />);
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByRole('tab', { name: 'label.test-case-plural' })
|
||||
).toHaveAttribute('aria-selected', 'true');
|
||||
expect(
|
||||
await screen.findByRole('tab', { name: 'label.pipeline' })
|
||||
).toHaveAttribute('aria-selected', 'false');
|
||||
const tabs = await screen.findAllByRole('tab');
|
||||
|
||||
expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
|
||||
expect(tabs[1]).toHaveAttribute('aria-selected', 'false');
|
||||
});
|
||||
|
||||
it('should display the initial summary data', async () => {
|
||||
@ -290,4 +307,95 @@ describe('QualityTab', () => {
|
||||
|
||||
expect(mockUseTableProfiler.onSettingButtonClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should display correct test case count in tab', async () => {
|
||||
(useTableProfiler as jest.Mock).mockReturnValue({
|
||||
...mockUseTableProfiler,
|
||||
testCasePaging: {
|
||||
...mockUseTableProfiler.testCasePaging,
|
||||
paging: { total: 25, after: 'after' },
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<QualityTab />);
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('test-cases-count')).toHaveTextContent(
|
||||
'25'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display correct pipeline count in tab', async () => {
|
||||
(getIngestionPipelines as jest.Mock).mockResolvedValueOnce({
|
||||
paging: { total: 5 },
|
||||
});
|
||||
|
||||
(useTableProfiler as jest.Mock).mockReturnValue({
|
||||
...mockUseTableProfiler,
|
||||
table: {
|
||||
...MOCK_TABLE,
|
||||
testSuite: {
|
||||
fullyQualifiedName: 'test.suite.name',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<QualityTab />);
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('pipeline-count')).toHaveTextContent('5');
|
||||
});
|
||||
|
||||
it('should show zero count when no test cases or pipelines exist', async () => {
|
||||
(useTableProfiler as jest.Mock).mockReturnValue({
|
||||
...mockUseTableProfiler,
|
||||
testCasePaging: {
|
||||
...mockUseTableProfiler.testCasePaging,
|
||||
paging: { total: 0, after: null },
|
||||
},
|
||||
table: {
|
||||
...MOCK_TABLE,
|
||||
testSuite: {
|
||||
fullyQualifiedName: 'test.suite.name',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<QualityTab />);
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('test-cases-count')).toHaveTextContent(
|
||||
'0'
|
||||
);
|
||||
expect(await screen.findByTestId('pipeline-count')).toHaveTextContent('0');
|
||||
});
|
||||
|
||||
it('should handle error in fetching pipeline count gracefully', async () => {
|
||||
const mockGetIngestionPipelines = jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
jest.mock('../../../../../rest/ingestionPipelineAPI', () => ({
|
||||
getIngestionPipelines: mockGetIngestionPipelines,
|
||||
}));
|
||||
|
||||
(useTableProfiler as jest.Mock).mockReturnValue({
|
||||
...mockUseTableProfiler,
|
||||
table: {
|
||||
...MOCK_TABLE,
|
||||
testSuite: {
|
||||
fullyQualifiedName: 'test.suite.name',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<QualityTab />);
|
||||
});
|
||||
|
||||
expect(await screen.findByTestId('pipeline-count')).toHaveTextContent('0');
|
||||
});
|
||||
});
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
PagingHandlerParams,
|
||||
} from '../../components/common/NextPrevious/NextPrevious.interface';
|
||||
import { OwnerLabel } from '../../components/common/OwnerLabel/OwnerLabel.component';
|
||||
import TabsLabel from '../../components/common/TabsLabel/TabsLabel.component';
|
||||
import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.interface';
|
||||
import DataQualityTab from '../../components/Database/Profiler/DataQualityTab/DataQualityTab';
|
||||
@ -49,12 +50,14 @@ import {
|
||||
EntityType,
|
||||
TabSpecificField,
|
||||
} from '../../enums/entity.enum';
|
||||
import { PipelineType } from '../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { TestCase } from '../../generated/tests/testCase';
|
||||
import { EntityReference, TestSuite } from '../../generated/tests/testSuite';
|
||||
import { Include } from '../../generated/type/include';
|
||||
import { usePaging } from '../../hooks/paging/usePaging';
|
||||
import { useFqn } from '../../hooks/useFqn';
|
||||
import { DataQualityPageTabs } from '../../pages/DataQuality/DataQualityPage.interface';
|
||||
import { getIngestionPipelines } from '../../rest/ingestionPipelineAPI';
|
||||
import {
|
||||
addTestCaseToLogicalTestSuite,
|
||||
getListTestCaseBySearch,
|
||||
@ -101,6 +104,8 @@ const TestSuiteDetailsPage = () => {
|
||||
useState<boolean>(false);
|
||||
const [sortOptions, setSortOptions] =
|
||||
useState<ListTestCaseParamsBySearch>(DEFAULT_SORT_ORDER);
|
||||
const [ingestionPipelineCount, setIngestionPipelineCount] =
|
||||
useState<number>(0);
|
||||
|
||||
const [slashedBreadCrumb, setSlashedBreadCrumb] = useState<
|
||||
TitleBreadcrumbProps['titleLinks']
|
||||
@ -176,7 +181,13 @@ const TestSuiteDetailsPage = () => {
|
||||
...param,
|
||||
limit: pageSize,
|
||||
});
|
||||
|
||||
const { paging: ingestionPipelinePaging } = await getIngestionPipelines({
|
||||
arrQueryFields: [],
|
||||
testSuite: testSuiteFQN,
|
||||
pipelineType: [PipelineType.TestSuite],
|
||||
limit: 0,
|
||||
});
|
||||
setIngestionPipelineCount(ingestionPipelinePaging.total);
|
||||
setTestCaseResult(response.data);
|
||||
handlePagingChange(response.paging);
|
||||
} catch {
|
||||
@ -363,7 +374,13 @@ const TestSuiteDetailsPage = () => {
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('label.test-case-plural'),
|
||||
label: (
|
||||
<TabsLabel
|
||||
count={pagingData.paging.total}
|
||||
id={EntityTabs.TEST_CASES}
|
||||
name={t('label.test-case-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.TEST_CASES,
|
||||
children: (
|
||||
<DataQualityTab
|
||||
@ -381,7 +398,13 @@ const TestSuiteDetailsPage = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
label: t('label.pipeline-plural'),
|
||||
label: (
|
||||
<TabsLabel
|
||||
count={ingestionPipelineCount}
|
||||
id={EntityTabs.PIPELINE}
|
||||
name={t('label.pipeline-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.PIPELINE,
|
||||
children: (
|
||||
<TestSuitePipelineTab isLogicalTestSuite testSuite={testSuite} />
|
||||
@ -399,6 +422,7 @@ const TestSuiteDetailsPage = () => {
|
||||
handleTestSuiteUpdate,
|
||||
handleSortTestCase,
|
||||
fetchTestCases,
|
||||
ingestionPipelineCount,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@ -14,7 +14,12 @@ import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
|
||||
import { mockEntityPermissions } from '../../pages/DatabaseSchemaPage/mocks/DatabaseSchemaPage.mock';
|
||||
import { getTestSuiteByName } from '../../rest/testAPI';
|
||||
import { getIngestionPipelines } from '../../rest/ingestionPipelineAPI';
|
||||
import {
|
||||
getListTestCaseBySearch,
|
||||
getTestSuiteByName,
|
||||
updateTestSuiteById,
|
||||
} from '../../rest/testAPI';
|
||||
import TestSuiteDetailsPage from './TestSuiteDetailsPage.component';
|
||||
|
||||
jest.mock('../../components/PageLayoutV1/PageLayoutV1', () => {
|
||||
@ -90,10 +95,10 @@ jest.mock('../../rest/testAPI', () => {
|
||||
addTestCaseToLogicalTestSuite: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
getListTestCase: jest
|
||||
getListTestCaseBySearch: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ data: [] })),
|
||||
ListTestCaseParams: jest
|
||||
ListTestCaseParamsBySearch: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ data: [] })),
|
||||
};
|
||||
@ -120,6 +125,28 @@ jest.mock('../../components/common/DomainLabel/DomainLabel.component', () => {
|
||||
.mockImplementation(() => <div>DomainLabel.component</div>),
|
||||
};
|
||||
});
|
||||
jest.mock('../../rest/ingestionPipelineAPI', () => ({
|
||||
getIngestionPipelines: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: [],
|
||||
paging: { total: 0 },
|
||||
})
|
||||
),
|
||||
}));
|
||||
jest.mock('../../components/common/OwnerLabel/OwnerLabel.component', () => ({
|
||||
OwnerLabel: jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="owner-label">OwnerLabel.component</div>
|
||||
)),
|
||||
}));
|
||||
jest.mock('../../components/common/TabsLabel/TabsLabel.component', () => {
|
||||
return jest.fn().mockImplementation(({ id, name }) => (
|
||||
<div className="w-full tabs-label-container" data-testid={id}>
|
||||
<div className="d-flex justify-between gap-2">{name}</div>
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
describe('TestSuiteDetailsPage component', () => {
|
||||
it('component should render', async () => {
|
||||
@ -177,4 +204,123 @@ describe('TestSuiteDetailsPage component', () => {
|
||||
await screen.findByText('ErrorPlaceHolder.component')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle domain update', async () => {
|
||||
const mockUpdateTestSuite = jest.fn().mockResolvedValue({
|
||||
id: '123',
|
||||
name: 'test-suite',
|
||||
domain: { id: 'domain-id', name: 'domain-name', type: 'domain' },
|
||||
});
|
||||
|
||||
(updateTestSuiteById as jest.Mock).mockImplementationOnce(
|
||||
mockUpdateTestSuite
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuiteDetailsPage />);
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByText('DomainLabel.component')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle description update', async () => {
|
||||
const mockUpdateTestSuite = jest.fn().mockResolvedValue({
|
||||
id: '123',
|
||||
name: 'test-suite',
|
||||
description: 'Updated description',
|
||||
});
|
||||
|
||||
(updateTestSuiteById as jest.Mock).mockImplementationOnce(
|
||||
mockUpdateTestSuite
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuiteDetailsPage />);
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByText('Description.component')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle test case pagination', async () => {
|
||||
const mockGetListTestCase = jest.fn().mockResolvedValue({
|
||||
data: [],
|
||||
paging: { total: 10 },
|
||||
});
|
||||
|
||||
(getListTestCaseBySearch as jest.Mock).mockImplementationOnce(
|
||||
mockGetListTestCase
|
||||
);
|
||||
|
||||
(getTestSuiteByName as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
id: 'test-suite-id',
|
||||
name: 'test-suite',
|
||||
})
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuiteDetailsPage />);
|
||||
});
|
||||
|
||||
await screen.findByTestId('test-cases');
|
||||
|
||||
expect(mockGetListTestCase).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
fields: ['testCaseResult', 'testDefinition', 'testSuite', 'incidentId'],
|
||||
testSuiteId: 'test-suite-id',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle add test case modal', async () => {
|
||||
await act(async () => {
|
||||
render(<TestSuiteDetailsPage />);
|
||||
});
|
||||
|
||||
const addButton = await screen.findByTestId('add-test-case-btn');
|
||||
|
||||
expect(addButton).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
addButton.click();
|
||||
});
|
||||
|
||||
// Modal should be visible after clicking add button
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle ingestion pipeline count', async () => {
|
||||
const mockGetIngestionPipelines = getIngestionPipelines as jest.Mock;
|
||||
mockGetIngestionPipelines.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
data: [],
|
||||
paging: { total: 5 },
|
||||
})
|
||||
);
|
||||
|
||||
(getTestSuiteByName as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
id: 'test-suite-id',
|
||||
name: 'test-suite',
|
||||
fullyQualifiedName: 'testSuiteFQN',
|
||||
})
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuiteDetailsPage />);
|
||||
});
|
||||
|
||||
expect(mockGetIngestionPipelines).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
testSuite: 'testSuiteFQN',
|
||||
pipelineType: ['TestSuite'],
|
||||
arrQueryFields: [],
|
||||
limit: 0,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user