mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-02 03:29:03 +00:00
This commit is contained in:
parent
d0611388f4
commit
4dc4053dbb
@ -19,6 +19,7 @@ import QueryString from 'qs';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { PAGE_SIZE_BASE } from '../../../../constants/constants';
|
||||
import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum';
|
||||
@ -29,6 +30,8 @@ import { Table as TableType } from '../../../../generated/entity/data/table';
|
||||
import { Operation } from '../../../../generated/entity/policies/policy';
|
||||
import { IngestionPipeline } from '../../../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { TestSuite } from '../../../../generated/tests/testCase';
|
||||
import { Paging } from '../../../../generated/type/paging';
|
||||
import { usePaging } from '../../../../hooks/paging/usePaging';
|
||||
import { useAirflowStatus } from '../../../../hooks/useAirflowStatus';
|
||||
import {
|
||||
deployIngestionPipelineById,
|
||||
@ -42,6 +45,7 @@ import { getServiceFromTestSuiteFQN } from '../../../../utils/TestSuiteUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils';
|
||||
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||
import ErrorPlaceHolderIngestion from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolderIngestion';
|
||||
import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface';
|
||||
import IngestionListTable from '../../../Settings/Services/Ingestion/IngestionListTable/IngestionListTable';
|
||||
|
||||
interface Props {
|
||||
@ -58,6 +62,8 @@ const TestSuitePipelineTab = ({
|
||||
const testSuiteFQN = testSuite?.fullyQualifiedName ?? testSuite?.name ?? '';
|
||||
|
||||
const { permissions } = usePermissionProvider();
|
||||
const pipelinePaging = usePaging(PAGE_SIZE_BASE);
|
||||
const { pageSize, handlePagingChange } = pipelinePaging;
|
||||
const history = useHistory();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@ -89,24 +95,44 @@ const TestSuitePipelineTab = ({
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const getAllIngestionWorkflows = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getIngestionPipelines({
|
||||
arrQueryFields: [
|
||||
TabSpecificField.OWNERS,
|
||||
TabSpecificField.PIPELINE_STATUSES,
|
||||
],
|
||||
testSuite: testSuiteFQN,
|
||||
pipelineType: [PipelineType.TestSuite],
|
||||
});
|
||||
setTestSuitePipelines(response.data);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [testSuiteFQN]);
|
||||
const getAllIngestionWorkflows = useCallback(
|
||||
async (paging?: Omit<Paging, 'total'>, limit?: number) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getIngestionPipelines({
|
||||
arrQueryFields: [
|
||||
TabSpecificField.OWNERS,
|
||||
TabSpecificField.PIPELINE_STATUSES,
|
||||
],
|
||||
testSuite: testSuiteFQN,
|
||||
pipelineType: [PipelineType.TestSuite],
|
||||
paging,
|
||||
limit: limit ?? pageSize,
|
||||
});
|
||||
setTestSuitePipelines(response.data);
|
||||
handlePagingChange(response.paging);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[testSuiteFQN, pageSize, handlePagingChange]
|
||||
);
|
||||
|
||||
const handlePipelinePageChange = useCallback(
|
||||
({ cursorType, currentPage }: PagingHandlerParams) => {
|
||||
const { paging, handlePageChange } = pipelinePaging;
|
||||
if (cursorType) {
|
||||
getAllIngestionWorkflows(
|
||||
{ [cursorType]: paging[cursorType] },
|
||||
pageSize
|
||||
);
|
||||
handlePageChange(currentPage);
|
||||
}
|
||||
},
|
||||
[getAllIngestionWorkflows, pipelinePaging]
|
||||
);
|
||||
|
||||
const handleAddPipelineRedirection = () => {
|
||||
history.push({
|
||||
@ -195,8 +221,8 @@ const TestSuitePipelineTab = ({
|
||||
}, [testSuitePipelines]);
|
||||
|
||||
useEffect(() => {
|
||||
getAllIngestionWorkflows();
|
||||
}, []);
|
||||
getAllIngestionWorkflows(undefined, pageSize);
|
||||
}, [pageSize]);
|
||||
|
||||
const emptyPlaceholder = useMemo(
|
||||
() =>
|
||||
@ -252,6 +278,7 @@ const TestSuitePipelineTab = ({
|
||||
handleIngestionListUpdate={handlePipelineListUpdate}
|
||||
handlePipelineIdToFetchStatus={handlePipelineIdToFetchStatus}
|
||||
ingestionData={testSuitePipelines}
|
||||
ingestionPagingInfo={pipelinePaging}
|
||||
isLoading={isLoading}
|
||||
pipelineIdToFetchStatus={pipelineIdToFetchStatus}
|
||||
serviceCategory={ServiceCategory.DATABASE_SERVICES}
|
||||
@ -259,6 +286,7 @@ const TestSuitePipelineTab = ({
|
||||
tableClassName="test-suite-pipeline-tab"
|
||||
triggerIngestion={handleTriggerIngestion}
|
||||
onIngestionWorkflowsUpdate={getAllIngestionWorkflows}
|
||||
onPageChange={handlePipelinePageChange}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@ -10,10 +10,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { act } from 'react-test-renderer';
|
||||
import { PAGE_SIZE_BASE } from '../../../../constants/constants';
|
||||
import { Table } from '../../../../generated/entity/data/table';
|
||||
import { IngestionPipeline } from '../../../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { useAirflowStatus } from '../../../../hooks/useAirflowStatus';
|
||||
import { getIngestionPipelines } from '../../../../rest/ingestionPipelineAPI';
|
||||
import TestSuitePipelineTab from './TestSuitePipelineTab.component';
|
||||
|
||||
@ -55,15 +58,124 @@ const mockTestSuite = {
|
||||
testCaseResultSummary: [],
|
||||
} as unknown as Table['testSuite'];
|
||||
|
||||
const mockPipelines = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'pipeline1',
|
||||
sourceConfig: {
|
||||
config: {
|
||||
testCases: ['test1', 'test2', 'test3'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'pipeline2',
|
||||
sourceConfig: {
|
||||
config: {
|
||||
testCases: ['test1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'pipeline3',
|
||||
sourceConfig: {
|
||||
config: {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockPaging = {
|
||||
after: 'after-id',
|
||||
before: 'before-id',
|
||||
total: 10,
|
||||
};
|
||||
|
||||
jest.mock('../../../../rest/ingestionPipelineAPI', () => {
|
||||
return {
|
||||
getIngestionPipelines: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ data: mockPipelines, paging: mockPaging })
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../hooks/useAirflowStatus', () => ({
|
||||
useAirflowStatus: jest.fn().mockReturnValue({
|
||||
isAirflowAvailable: true,
|
||||
isFetchingStatus: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useHistory: jest.fn(),
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
pathname: '/test/path',
|
||||
search: '',
|
||||
hash: '',
|
||||
state: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../context/PermissionProvider/PermissionProvider', () => ({
|
||||
usePermissionProvider: jest.fn().mockReturnValue({
|
||||
permissions: {},
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../../Settings/Services/Ingestion/IngestionListTable/IngestionListTable',
|
||||
() => {
|
||||
return function MockIngestionListTable({
|
||||
ingestionData,
|
||||
onPageChange,
|
||||
}: {
|
||||
ingestionData: IngestionPipeline[];
|
||||
onPageChange: ({
|
||||
cursorType,
|
||||
currentPage,
|
||||
}: {
|
||||
cursorType: string;
|
||||
currentPage: number;
|
||||
}) => void;
|
||||
}) {
|
||||
return (
|
||||
<div data-testid="test-suite-pipeline-tab">
|
||||
{ingestionData.map((pipeline) => (
|
||||
<div key={pipeline.id}>{pipeline.name}</div>
|
||||
))}
|
||||
<button
|
||||
onClick={() =>
|
||||
onPageChange({ cursorType: 'after', currentPage: 2 })
|
||||
}>
|
||||
Next Page
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'../../../common/ErrorWithPlaceholder/ErrorPlaceHolderIngestion',
|
||||
() => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="error-placeholder-ingestion">
|
||||
Airflow not available
|
||||
</div>
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
describe('TestSuite Pipeline component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('getIngestionPipelines API should call on page load', async () => {
|
||||
const mockGetIngestionPipelines = getIngestionPipelines as jest.Mock;
|
||||
await act(async () => {
|
||||
@ -74,6 +186,107 @@ describe('TestSuite Pipeline component', () => {
|
||||
arrQueryFields: ['owners', 'pipelineStatuses'],
|
||||
pipelineType: ['TestSuite'],
|
||||
testSuite: mockTestSuite?.fullyQualifiedName,
|
||||
limit: PAGE_SIZE_BASE,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call getIngestionPipelines with correct paging parameters', async () => {
|
||||
const mockGetIngestionPipelines = getIngestionPipelines as jest.Mock;
|
||||
await act(async () => {
|
||||
render(<TestSuitePipelineTab testSuite={mockTestSuite} />);
|
||||
});
|
||||
|
||||
// Initial call with default page size
|
||||
expect(mockGetIngestionPipelines).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
limit: PAGE_SIZE_BASE,
|
||||
})
|
||||
);
|
||||
|
||||
// Click next page button
|
||||
const nextPageButton = screen.getByText('Next Page');
|
||||
await act(async () => {
|
||||
nextPageButton.click();
|
||||
});
|
||||
|
||||
// Verify call with after cursor
|
||||
expect(mockGetIngestionPipelines).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
paging: { after: 'after-id' },
|
||||
limit: PAGE_SIZE_BASE,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should update pipeline list when page changes', async () => {
|
||||
const updatedMockPipelines = [
|
||||
{
|
||||
id: '4',
|
||||
name: 'pipeline4',
|
||||
sourceConfig: { config: {} },
|
||||
},
|
||||
];
|
||||
|
||||
const mockGetIngestionPipelines = getIngestionPipelines as jest.Mock;
|
||||
mockGetIngestionPipelines
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ data: mockPipelines, paging: mockPaging })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
data: updatedMockPipelines,
|
||||
paging: { ...mockPaging, after: null },
|
||||
})
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuitePipelineTab testSuite={mockTestSuite} />);
|
||||
});
|
||||
|
||||
// Initial render should show first page pipelines
|
||||
expect(screen.getByText('pipeline1')).toBeInTheDocument();
|
||||
|
||||
// Click next page button
|
||||
const nextPageButton = screen.getByText('Next Page');
|
||||
await act(async () => {
|
||||
nextPageButton.click();
|
||||
});
|
||||
|
||||
// Should show second page pipeline
|
||||
expect(screen.getByText('pipeline4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show error placeholder when airflow is not available', async () => {
|
||||
// Mock airflow status as unavailable
|
||||
(useAirflowStatus as jest.Mock).mockReturnValue({
|
||||
isAirflowAvailable: false,
|
||||
isFetchingStatus: false,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuitePipelineTab testSuite={mockTestSuite} />);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByTestId('error-placeholder-ingestion')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show loading state while fetching airflow status', async () => {
|
||||
// Mock airflow status as loading
|
||||
(useAirflowStatus as jest.Mock).mockReturnValue({
|
||||
isAirflowAvailable: true,
|
||||
isFetchingStatus: true,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<TestSuitePipelineTab testSuite={mockTestSuite} />);
|
||||
});
|
||||
|
||||
// Component should be in loading state
|
||||
expect(screen.queryByTestId('error-placeholder')).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('error-placeholder-ingestion')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -43,6 +43,7 @@ import { EntityTabs, EntityType } from '../../../../../enums/entity.enum';
|
||||
import { ProfilerDashboardType } from '../../../../../enums/table.enum';
|
||||
import { TestCaseStatus } from '../../../../../generated/tests/testCase';
|
||||
import LimitWrapper from '../../../../../hoc/LimitWrapper';
|
||||
import useCustomLocation from '../../../../../hooks/useCustomLocation/useCustomLocation';
|
||||
import { useFqn } from '../../../../../hooks/useFqn';
|
||||
import {
|
||||
ListTestCaseParamsBySearch,
|
||||
@ -96,6 +97,7 @@ export const QualityTab = () => {
|
||||
}, [permissions]);
|
||||
const { fqn: datasetFQN } = useFqn();
|
||||
const history = useHistory();
|
||||
const location = useCustomLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selectedTestCaseStatus, setSelectedTestCaseStatus] =
|
||||
@ -249,6 +251,14 @@ export const QualityTab = () => {
|
||||
history.push(getAddDataQualityTableTestPath(type, datasetFQN));
|
||||
};
|
||||
|
||||
const handleTabChange = () => {
|
||||
history.replace({
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
state: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const addButtonContent = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -333,7 +343,7 @@ export const QualityTab = () => {
|
||||
<SummaryPanel testSummary={testCaseSummary ?? INITIAL_TEST_SUMMARY} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Tabs items={tabs} />
|
||||
<Tabs items={tabs} onChange={handleTabChange} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@ -121,6 +121,14 @@ jest.mock('../../../../../hoc/LimitWrapper', () => {
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../hooks/useCustomLocation/useCustomLocation', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(() => ({
|
||||
pathname: '/test-path',
|
||||
search: '?test=value',
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('QualityTab', () => {
|
||||
it('should render QualityTab', async () => {
|
||||
await act(async () => {
|
||||
|
||||
@ -24,7 +24,7 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PAGE_SIZE } from '../../../../constants/constants';
|
||||
import { PAGE_SIZE_BASE } from '../../../../constants/constants';
|
||||
import { mockDatasetData } from '../../../../constants/mockTourData.constants';
|
||||
import {
|
||||
DEFAULT_RANGE_DATA,
|
||||
@ -71,7 +71,7 @@ export const TableProfilerProvider = ({
|
||||
const { t } = useTranslation();
|
||||
const { fqn: datasetFQN } = useFqn();
|
||||
const { isTourOpen } = useTourProvider();
|
||||
const testCasePaging = usePaging(PAGE_SIZE);
|
||||
const testCasePaging = usePaging(PAGE_SIZE_BASE);
|
||||
const location = useCustomLocation();
|
||||
// profiler has its own api but sent's the data in Table type
|
||||
const [tableProfiler, setTableProfiler] = useState<Table>();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user