= [
{ name: 'Select/Add Test Suite', step: 1 },
{ name: 'Configure Test Case', step: 2 },
- // { name: 'Schedule Interval', step: 3 },
];
diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts
index 773328b3e76..4ddc3e35b26 100644
--- a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts
@@ -34,6 +34,7 @@ export enum EntityType {
BOT = 'bot',
ROLE = 'role',
POLICY = 'policy',
+ TEST_SUITE = 'testSuite',
}
export enum AssetsType {
diff --git a/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts b/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts
index 45e81533917..8640715491a 100644
--- a/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts
@@ -112,6 +112,8 @@ const jsonData = {
'fetch-table-profiler-config-error':
'Error while fetching table profiler config!',
'fetch-column-test-error': 'Error while fetching column test case!',
+ 'fetch-test-suite-error': 'Error while fetching test suite',
+ 'fetch-test-cases-error': 'Error while fetching test cases',
'test-connection-error': 'Error while testing connection!',
@@ -144,6 +146,7 @@ const jsonData = {
'join-team-error': 'Error while joining the team!',
'leave-team-error': 'Error while leaving the team!',
+ 'update-test-suite-error': 'Error while updating test suite',
},
'api-success-messages': {
'create-conversation': 'Conversation created successfully!',
@@ -161,6 +164,7 @@ const jsonData = {
'user-restored-success': 'User restored successfully!',
'update-profile-congif-success': 'Profile config updated successfully!',
+ 'update-test-case-success': 'Test case updated successfully!',
},
'form-error-messages': {
'empty-email': 'Email is required.',
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ProfilerDashboardPage/ProfilerDashboardPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ProfilerDashboardPage/ProfilerDashboardPage.tsx
index efe16306d3f..18e1dcfb3e1 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/ProfilerDashboardPage/ProfilerDashboardPage.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/ProfilerDashboardPage/ProfilerDashboardPage.tsx
@@ -83,6 +83,10 @@ const ProfilerDashboardPage = () => {
}
};
+ const handleTestCaseUpdate = () => {
+ fetchTestCases(generateEntityLink(entityTypeFQN));
+ };
+
const fetchTableEntity = async () => {
try {
const fqn = isColumnView
@@ -155,6 +159,7 @@ const ProfilerDashboardPage = () => {
table={table}
testCases={testCases}
onTableChange={updateTableHandler}
+ onTestCaseUpdate={handleTestCaseUpdate}
/>
);
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx
new file mode 100644
index 00000000000..93a0952a233
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2022 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Col, Row } from 'antd';
+import { AxiosError } from 'axios';
+import { compare } from 'fast-json-patch';
+import { camelCase, startCase } from 'lodash';
+import { ExtraInfo } from 'Models';
+import React, { useEffect, useMemo, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import {
+ getListTestCase,
+ getTestSuiteByName,
+ ListTestCaseParams,
+ updateTestSuiteById,
+} from '../../axiosAPIs/testAPI';
+import TabsPane from '../../components/common/TabsPane/TabsPane';
+import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface';
+import PageContainer from '../../components/containers/PageContainer';
+import Loader from '../../components/Loader/Loader';
+import TestCasesTab from '../../components/TestCasesTab/TestCasesTab.component';
+import TestSuiteDetails from '../../components/TestSuiteDetails/TestSuiteDetails.component';
+import TestSuitePipelineTab from '../../components/TestSuitePipelineTab/TestSuitePipelineTab.component';
+import {
+ getTeamAndUserDetailsPath,
+ INITIAL_PAGING_VALUE,
+ PAGE_SIZE,
+ pagingObject,
+} from '../../constants/constants';
+import {
+ GlobalSettingOptions,
+ GlobalSettingsMenuCategory,
+} from '../../constants/globalSettings.constants';
+import { OwnerType } from '../../enums/user.enum';
+import { TestCase } from '../../generated/tests/testCase';
+import { TestSuite } from '../../generated/tests/testSuite';
+import { Paging } from '../../generated/type/paging';
+import jsonData from '../../jsons/en';
+import { getEntityName, getEntityPlaceHolder } from '../../utils/CommonUtils';
+import { getSettingPath } from '../../utils/RouterUtils';
+import { showErrorToast } from '../../utils/ToastUtils';
+import './TestSuiteDetailsPage.styles.less';
+
+const TestSuiteDetailsPage = () => {
+ const { testSuiteFQN } = useParams
>();
+ const [testSuite, setTestSuite] = useState();
+ const [isDescriptionEditable, setIsDescriptionEditable] = useState(false);
+ const [isDeleteWidgetVisible, setIsDeleteWidgetVisible] = useState(false);
+ const [isTestCaseLoaded, setIsTestCaseLoaded] = useState(false);
+ const [testCaseResult, setTestCaseResult] = useState>([]);
+ const [currentPage, setCurrentPage] = useState(INITIAL_PAGING_VALUE);
+ const [testCasesPaging, setTestCasesPaging] = useState(pagingObject);
+
+ const [slashedBreadCrumb, setSlashedBreadCrumb] = useState<
+ TitleBreadcrumbProps['titleLinks']
+ >([]);
+
+ const [activeTab, setActiveTab] = useState(1);
+
+ const tabs = [
+ {
+ name: 'Test Cases',
+ isProtected: false,
+ position: 1,
+ },
+ {
+ name: 'Pipeline',
+ isProtected: false,
+ position: 2,
+ },
+ ];
+
+ const { testSuiteDescription, testSuiteId, testOwner } = useMemo(() => {
+ return {
+ testOwner: testSuite?.owner,
+ testSuiteId: testSuite?.id,
+ testSuiteDescription: testSuite?.description,
+ };
+ }, [testSuite]);
+
+ const saveAndUpdateTestSuiteData = (updatedData: TestSuite) => {
+ const jsonPatch = compare(testSuite as TestSuite, updatedData);
+
+ return updateTestSuiteById(testSuiteId as string, jsonPatch);
+ };
+
+ const descriptionHandler = (value: boolean) => {
+ setIsDescriptionEditable(value);
+ };
+
+ const fetchTestCases = async (param?: ListTestCaseParams, limit?: number) => {
+ setIsTestCaseLoaded(false);
+ try {
+ const response = await getListTestCase({
+ fields: 'testCaseResult,testDefinition',
+ testSuiteId: testSuiteId,
+ limit: limit || PAGE_SIZE,
+ before: param && param.before,
+ after: param && param.after,
+ ...param,
+ });
+
+ setTestCaseResult(response.data);
+ setTestCasesPaging(response.paging);
+ } catch {
+ setTestCaseResult([]);
+ showErrorToast(jsonData['api-error-messages']['fetch-test-cases-error']);
+ } finally {
+ setIsTestCaseLoaded(true);
+ }
+ };
+
+ const afterSubmitAction = () => {
+ fetchTestCases();
+ };
+
+ const fetchTestSuiteByName = async () => {
+ try {
+ const response = await getTestSuiteByName(testSuiteFQN, {
+ fields: 'owner',
+ });
+ setSlashedBreadCrumb([
+ {
+ name: 'Test Suites',
+ url: getSettingPath(
+ GlobalSettingsMenuCategory.DATA_QUALITY,
+ GlobalSettingOptions.TEST_SUITE
+ ),
+ },
+ {
+ name: startCase(
+ camelCase(response?.fullyQualifiedName || response?.name)
+ ),
+ url: '',
+ },
+ ]);
+ setTestSuite(response);
+ fetchTestCases({ testSuiteId: response.id });
+ } catch (error) {
+ setTestSuite(undefined);
+ showErrorToast(
+ error as AxiosError,
+ jsonData['api-error-messages']['fetch-test-suite-error']
+ );
+ }
+ };
+
+ const onUpdateOwner = (updatedOwner: TestSuite['owner']) => {
+ if (updatedOwner) {
+ const updatedTestSuite = {
+ ...testSuite,
+ owner: {
+ ...testSuite?.owner,
+ ...updatedOwner,
+ },
+ } as TestSuite;
+
+ saveAndUpdateTestSuiteData(updatedTestSuite)
+ .then((res) => {
+ if (res) {
+ setTestSuite(res);
+ } else {
+ showErrorToast(
+ jsonData['api-error-messages']['unexpected-server-response']
+ );
+ }
+ })
+ .catch((err: AxiosError) => {
+ showErrorToast(
+ err,
+ jsonData['api-error-messages']['update-owner-error']
+ );
+ });
+ }
+ };
+
+ const onDescriptionUpdate = (updatedHTML: string) => {
+ if (testSuite?.description !== updatedHTML) {
+ const updatedTestSuite = { ...testSuite, description: updatedHTML };
+ saveAndUpdateTestSuiteData(updatedTestSuite as TestSuite)
+ .then((res) => {
+ if (res) {
+ setTestSuite(res);
+ } else {
+ throw jsonData['api-error-messages']['unexpected-server-response'];
+ }
+ })
+ .catch((error: AxiosError) => {
+ showErrorToast(
+ error,
+ jsonData['api-error-messages']['update-test-suite-error']
+ );
+ })
+ .finally(() => {
+ descriptionHandler(false);
+ });
+ } else {
+ descriptionHandler(false);
+ }
+ };
+
+ const onSetActiveValue = (tabValue: number) => {
+ setActiveTab(tabValue);
+ };
+
+ const handleDescriptionUpdate = (updatedHTML: string) => {
+ onDescriptionUpdate(updatedHTML);
+ };
+
+ const handleDeleteWidgetVisible = (isVisible: boolean) => {
+ setIsDeleteWidgetVisible(isVisible);
+ };
+
+ const handleTestCasePaging = (
+ cursorValue: string | number,
+ activePage?: number | undefined
+ ) => {
+ setCurrentPage(activePage as number);
+ fetchTestCases({
+ [cursorValue]: testCasesPaging[cursorValue as keyof Paging] as string,
+ });
+ };
+
+ const extraInfo: Array = useMemo(
+ () => [
+ {
+ key: 'Owner',
+ value:
+ testOwner?.type === 'team'
+ ? getTeamAndUserDetailsPath(testOwner?.name || '')
+ : getEntityName(testOwner) || '',
+ placeholderText:
+ getEntityPlaceHolder(
+ (testOwner?.displayName as string) || (testOwner?.name as string),
+ testOwner?.deleted
+ ) || '',
+ isLink: testOwner?.type === 'team',
+ openInNewTab: false,
+ profileName:
+ testOwner?.type === OwnerType.USER ? testOwner?.name : undefined,
+ },
+ ],
+ [testOwner]
+ );
+
+ useEffect(() => {
+ fetchTestSuiteByName();
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+ {activeTab === 1 && (
+ <>
+ {isTestCaseLoaded ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+ {activeTab === 2 && }
+
+
+
+
+ );
+};
+
+export default TestSuiteDetailsPage;
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.styles.less b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.styles.less
new file mode 100644
index 00000000000..a91ce1e3108
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.styles.less
@@ -0,0 +1,10 @@
+.test-suite-description {
+ .description-inner-main-container {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .rich-text-editor-container {
+ padding-left: 0;
+ }
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteIngestionPage/TestSuiteIngestionPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteIngestionPage/TestSuiteIngestionPage.tsx
new file mode 100644
index 00000000000..ebeb2a4d5a3
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteIngestionPage/TestSuiteIngestionPage.tsx
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2021 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Empty } from 'antd';
+import { AxiosError } from 'axios';
+import { isUndefined, startCase } from 'lodash';
+import React, { useEffect, useState } from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+import { getIngestionPipelineByFqn } from '../../axiosAPIs/ingestionPipelineAPI';
+import { getTestSuiteByName } from '../../axiosAPIs/testAPI';
+import RightPanel from '../../components/AddDataQualityTest/components/RightPanel';
+import { INGESTION_DATA } from '../../components/AddDataQualityTest/rightPanelData';
+import TestSuiteIngestion from '../../components/AddDataQualityTest/TestSuiteIngestion';
+import TitleBreadcrumb from '../../components/common/title-breadcrumb/title-breadcrumb.component';
+import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface';
+import PageContainerV1 from '../../components/containers/PageContainerV1';
+import PageLayout from '../../components/containers/PageLayout';
+import Loader from '../../components/Loader/Loader';
+import {
+ GlobalSettingOptions,
+ GlobalSettingsMenuCategory,
+} from '../../constants/globalSettings.constants';
+import { PageLayoutType } from '../../enums/layout.enum';
+import { IngestionPipeline } from '../../generated/entity/services/ingestionPipelines/ingestionPipeline';
+import { TestSuite } from '../../generated/tests/testSuite';
+import jsonData from '../../jsons/en';
+import { getSettingPath, getTestSuitePath } from '../../utils/RouterUtils';
+import { showErrorToast } from '../../utils/ToastUtils';
+
+const TestSuiteIngestionPage = () => {
+ const { testSuiteFQN, ingestionFQN } = useParams>();
+
+ const history = useHistory();
+ const [isLoading, setIsLoading] = useState(true);
+ const [testSuite, setTestSuite] = useState();
+ const [ingestionPipeline, setIngestionPipeline] =
+ useState();
+ const [slashedBreadCrumb, setSlashedBreadCrumb] = useState<
+ TitleBreadcrumbProps['titleLinks']
+ >([]);
+
+ const fetchIngestionByName = async () => {
+ setIsLoading(true);
+ try {
+ const response = await getIngestionPipelineByFqn(ingestionFQN);
+
+ setIngestionPipeline(response);
+ } catch (error) {
+ showErrorToast(
+ error as AxiosError,
+ jsonData['api-error-messages']['fetch-ingestion-error']
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const fetchTestSuiteByName = async () => {
+ setIsLoading(true);
+ try {
+ const response = await getTestSuiteByName(testSuiteFQN, {
+ fields: 'owner',
+ });
+ setSlashedBreadCrumb([
+ {
+ name: 'Test Suites',
+ url: getSettingPath(
+ GlobalSettingsMenuCategory.DATA_QUALITY,
+ GlobalSettingOptions.TEST_SUITE
+ ),
+ },
+ {
+ name: startCase(response.displayName || response.name),
+ url: getTestSuitePath(response.fullyQualifiedName || ''),
+ },
+ {
+ name: `${ingestionFQN ? 'Edit' : 'Add'} Ingestion`,
+ url: '',
+ },
+ ]);
+ setTestSuite(response);
+
+ if (ingestionFQN) {
+ await fetchIngestionByName();
+ }
+ } catch (error) {
+ setTestSuite(undefined);
+ showErrorToast(
+ error as AxiosError,
+ jsonData['api-error-messages']['fetch-test-suite-error']
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleCancelBtn = () => {
+ history.push(getTestSuitePath(testSuiteFQN || ''));
+ };
+
+ useEffect(() => {
+ fetchTestSuiteByName();
+ }, []);
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (isUndefined(testSuite)) {
+ return ;
+ }
+
+ return (
+
+
+
}
+ layout={PageLayoutType['2ColRTL']}
+ rightPanel={
}>
+
+
+
+
+ );
+};
+
+export default TestSuiteIngestionPage;
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TestSuitePage/TestSuitePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuitePage/TestSuitePage.tsx
new file mode 100644
index 00000000000..0780275713e
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuitePage/TestSuitePage.tsx
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2022 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Col, Row, Table, Typography } from 'antd';
+import { ColumnsType } from 'antd/lib/table';
+import React, { useEffect, useMemo, useState } from 'react';
+import { Link } from 'react-router-dom';
+import { getListTestSuites } from '../../axiosAPIs/testAPI';
+import Ellipses from '../../components/common/Ellipses/Ellipses';
+import NextPrevious from '../../components/common/next-previous/NextPrevious';
+import {
+ INITIAL_PAGING_VALUE,
+ PAGE_SIZE,
+ pagingObject,
+} from '../../constants/constants';
+import { TestSuite } from '../../generated/tests/testSuite';
+import { Paging } from '../../generated/type/paging';
+import { getTestSuitePath } from '../../utils/RouterUtils';
+const { Text } = Typography;
+
+const TestSuitePage = () => {
+ const [testSuites, setTestSuites] = useState>([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [testSuitePage, setTestSuitePage] = useState(INITIAL_PAGING_VALUE);
+ const [testSuitePaging, setTestSuitePaging] = useState(pagingObject);
+
+ const fetchTestSuites = async (param?: Record) => {
+ try {
+ setIsLoading(true);
+ const response = await getListTestSuites({
+ fields: 'owner,tests',
+ limit: PAGE_SIZE,
+ before: param && param.before,
+ after: param && param.after,
+ });
+ setTestSuites(response.data);
+ setTestSuitePaging(response.paging);
+ } catch (err) {
+ setTestSuites([]);
+ setTestSuitePaging(pagingObject);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const columns = useMemo(() => {
+ const col: ColumnsType = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ render: (_, record) => (
+ {record.name}
+ ),
+ },
+ {
+ title: 'Description',
+ dataIndex: 'description',
+ key: 'description',
+ width: 300,
+ render: (_, record) => (
+
+ {record.description}
+
+ ),
+ },
+ {
+ title: 'No. of Test',
+ dataIndex: 'noOfTests',
+ key: 'noOfTests',
+ render: (_, record) => {record?.tests?.length} Tests ,
+ },
+ {
+ title: 'Owner',
+ dataIndex: 'owner',
+ key: 'owner',
+ render: (_, record) => {record?.owner?.displayName} ,
+ },
+ ];
+
+ return col;
+ }, [testSuites]);
+
+ const testSuitePagingHandler = (
+ cursorValue: string | number,
+ activePage?: number
+ ) => {
+ setTestSuitePage(activePage as number);
+ fetchTestSuites({
+ [cursorValue]: testSuitePaging[cursorValue as keyof Paging] as string,
+ });
+ };
+
+ useEffect(() => {
+ fetchTestSuites();
+ }, []);
+
+ return (
+
+
+ ({ ...test, key: test.name }))}
+ loading={isLoading}
+ pagination={false}
+ size="small"
+ />
+
+ {testSuites.length > PAGE_SIZE && (
+
+
+
+ )}
+
+ );
+};
+
+export default TestSuitePage;
diff --git a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx
index 3812bd4fd1d..e5f7fda85c8 100644
--- a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx
@@ -18,7 +18,6 @@ import AppState from '../AppState';
import { usePermissionProvider } from '../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { ROUTES } from '../constants/constants';
-import AddDataQualityTestPage from '../pages/AddDataQualityTestPage/AddDataQualityTestPage';
import { Operation } from '../generated/entity/policies/policy';
import { checkPermission } from '../utils/PermissionsUtils';
import AdminProtectedRoute from './AdminProtectedRoute';
@@ -34,6 +33,24 @@ const ProfilerDashboardPage = withSuspenseFallback(
)
);
+const TestSuiteIngestionPage = withSuspenseFallback(
+ React.lazy(
+ () => import('../pages/TestSuiteIngestionPage/TestSuiteIngestionPage')
+ )
+);
+
+const TestSuiteDetailsPage = withSuspenseFallback(
+ React.lazy(
+ () => import('../pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component')
+ )
+);
+
+const AddDataQualityTestPage = withSuspenseFallback(
+ React.lazy(
+ () => import('../pages/AddDataQualityTestPage/AddDataQualityTestPage')
+ )
+);
+
const AddCustomProperty = withSuspenseFallback(
React.lazy(
() =>
@@ -466,7 +483,17 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
component={GlobalSettingPage}
path={ROUTES.SETTINGS_WITH_TAB_FQN}
/>
-
+
+
+
);
diff --git a/openmetadata-ui/src/main/resources/ui/src/router/GlobalSettingRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/router/GlobalSettingRouter.tsx
index fa09baed65d..2c13ca433d3 100644
--- a/openmetadata-ui/src/main/resources/ui/src/router/GlobalSettingRouter.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/router/GlobalSettingRouter.tsx
@@ -66,6 +66,9 @@ const SlackSettingsPage = withSuspenseFallback(
() => import('../pages/SlackSettingsPage/SlackSettingsPage.component')
)
);
+const TestSuitePage = withSuspenseFallback(
+ React.lazy(() => import('../pages/TestSuitePage/TestSuitePage'))
+);
const MsTeamsPage = withSuspenseFallback(
React.lazy(() => import('../pages/MsTeamsPage/MsTeamsPage.component'))
);
@@ -100,6 +103,14 @@ const GlobalSettingRouter = () => {
true
)}
/>
+
{/* Roles route start
* Do not change the order of these route
*/}
diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less
new file mode 100644
index 00000000000..ad15190cd94
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 Collate
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// common css utils file
+
+.flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.mx-auto {
+ margin-right: auto;
+ margin-left: auto;
+}
diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/index.js b/openmetadata-ui/src/main/resources/ui/src/styles/index.js
index d62429e607c..d60d2aeead2 100644
--- a/openmetadata-ui/src/main/resources/ui/src/styles/index.js
+++ b/openmetadata-ui/src/main/resources/ui/src/styles/index.js
@@ -14,6 +14,7 @@
import 'tailwindcss/tailwind.css';
import '../fonts/Inter/Inter-VariableFont_slnt,wght.ttf';
import './antd-master.less';
+import './app.less';
import './components/glossary.less';
import './components/step.less';
import './fonts.css';
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx
index 9b621d531ba..25e2bbc4be9 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx
@@ -28,6 +28,7 @@ import { reactLocalStorage } from 'reactjs-localstorage';
import AppState from '../AppState';
import { getFeedCount } from '../axiosAPIs/feedsAPI';
import { Button } from '../components/buttons/Button/Button';
+import PopOver from '../components/common/popover/PopOver';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import {
imageTypes,
@@ -49,6 +50,7 @@ import { Table } from '../generated/entity/data/table';
import { Topic } from '../generated/entity/data/topic';
import { ThreadTaskStatus, ThreadType } from '../generated/entity/feed/thread';
import { Policy } from '../generated/entity/policies/policy';
+import { IngestionPipeline } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
import { Role } from '../generated/entity/teams/role';
import { Team } from '../generated/entity/teams/team';
import { EntityReference, User } from '../generated/entity/teams/user';
@@ -823,3 +825,57 @@ export const getTeamsUser = (
return;
};
+
+export const getIngestionStatuses = (ingestion: IngestionPipeline) => {
+ const lastFiveIngestions = ingestion.pipelineStatuses
+ ?.sort((a, b) => {
+ // Turn your strings into millis, and then subtract them
+ // to get a value that is either negative, positive, or zero.
+ const date1 = new Date(a.startDate || '');
+ const date2 = new Date(b.startDate || '');
+
+ return date1.getTime() - date2.getTime();
+ })
+ .slice(Math.max(ingestion.pipelineStatuses.length - 5, 0));
+
+ return lastFiveIngestions?.map((r, i) => {
+ const status =
+ i === lastFiveIngestions.length - 1 ? (
+
+ {capitalize(r.state)}
+
+ ) : (
+
+ );
+
+ return r?.endDate || r?.startDate || r?.timestamp ? (
+
+ {r.timestamp ? (
+ Execution Date: {new Date(r.timestamp).toUTCString()}
+ ) : null}
+ {r.startDate ? (
+ Start Date: {new Date(r.startDate).toUTCString()}
+ ) : null}
+ {r.endDate ? (
+ End Date: {new Date(r.endDate).toUTCString()}
+ ) : null}
+
+ }
+ key={i}
+ position="bottom"
+ theme="light"
+ trigger="mouseenter">
+ {status}
+
+ ) : (
+ status
+ );
+ });
+};
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx
index 0b16a247be8..55c756266fd 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx
@@ -152,6 +152,20 @@ export const getGlobalSettingsMenuWithPermission = (
},
],
},
+ {
+ category: 'Data Quality',
+ items: [
+ {
+ label: 'Test Suite',
+ isProtected: checkPermission(
+ Operation.ViewAll,
+ ResourceEntity.TEST_SUITE,
+ permissions
+ ),
+ icon: ,
+ },
+ ],
+ },
{
category: 'Custom Attributes',
items: [
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts
index fb194084d46..0f86e7a9a69 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts
@@ -28,6 +28,7 @@ import {
PLACEHOLDER_RULE_NAME,
PLACEHOLDER_SETTING_CATEGORY,
PLACEHOLDER_TAG_NAME,
+ PLACEHOLDER_TEST_SUITE_FQN,
ROUTES,
} from '../constants/constants';
import { initialFilterQS } from '../constants/explore.constants';
@@ -343,3 +344,26 @@ export const getAddDataQualityTableTestPath = (
return path;
};
+
+export const getTestSuitePath = (testSuiteName: string) => {
+ let path = ROUTES.TEST_SUITES;
+ path = path.replace(PLACEHOLDER_TEST_SUITE_FQN, testSuiteName);
+
+ return path;
+};
+
+export const getTestSuiteIngestionPath = (
+ testSuiteName: string,
+ ingestionFQN?: string
+) => {
+ let path = ingestionFQN
+ ? ROUTES.TEST_SUITES_EDIT_INGESTION
+ : ROUTES.TEST_SUITES_ADD_INGESTION;
+ path = path.replace(PLACEHOLDER_TEST_SUITE_FQN, testSuiteName);
+
+ if (ingestionFQN) {
+ path = path.replace(PLACEHOLDER_ROUTE_INGESTION_FQN, ingestionFQN);
+ }
+
+ return path;
+};