diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.ts index f68173d76ee..989ca9223ee 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.ts @@ -31,7 +31,7 @@ import { scheduleIngestion, } from '../../common/Utils/Ingestion'; import { getToken } from '../../common/Utils/LocalStorage'; -import { addOwner, removeOwner, updateOwner } from '../../common/Utils/Owner'; +import { removeOwner, updateOwner } from '../../common/Utils/Owner'; import { goToServiceListingPage, Services } from '../../common/Utils/Services'; import { DATA_QUALITY_SAMPLE_DATA_TABLE, @@ -67,25 +67,11 @@ const goToProfilerTab = () => { cy.get('[data-testid="profiler"]').should('be.visible').click(); }; -const clickOnTestSuite = (testSuiteName) => { - cy.get('[data-testid="test-suite-container"]').then(($body) => { - if ($body.find(`[data-testid="${testSuiteName}"]`).length) { - cy.get(`[data-testid="${testSuiteName}"]`).scrollIntoView().click(); - } else { - if ($body.find('[data-testid="next"]').length) { - cy.get('[data-testid="next"]').click(); - verifyResponseStatusCode('@testSuite', 200); - clickOnTestSuite(testSuiteName); - } else { - throw new Error('Test Suite not found'); - } - } - }); -}; -const visitTestSuiteDetailsPage = (testSuiteName) => { + +const visitTestSuiteDetailsPage = (testSuiteName: string) => { interceptURL( 'GET', - '/api/v1/dataQuality/testSuites?*testSuiteType=logical*', + '/api/v1/dataQuality/testSuites/search/list?*testSuiteType=logical*', 'testSuite' ); interceptURL('GET', '/api/v1/dataQuality/testCases?fields=*', 'testCase'); @@ -94,7 +80,14 @@ const visitTestSuiteDetailsPage = (testSuiteName) => { cy.get('[data-testid="by-test-suites"]').click(); verifyResponseStatusCode('@testSuite', 200); - clickOnTestSuite(testSuiteName); + interceptURL( + 'GET', + `/api/v1/dataQuality/testSuites/search/list?*${testSuiteName}*testSuiteType=logical*`, + 'testSuiteBySearch' + ); + cy.get('[data-testid="search-bar-container"]').type(testSuiteName); + verifyResponseStatusCode('@testSuiteBySearch', 200); + cy.get(`[data-testid="${testSuiteName}"]`).scrollIntoView().click(); }; const verifyFilterTestCase = () => { @@ -476,7 +469,7 @@ describe( const testCaseName = 'column_value_max_to_be_between'; interceptURL( 'GET', - '/api/v1/dataQuality/testSuites?*testSuiteType=logical*', + '/api/v1/dataQuality/testSuites/search/list?*testSuiteType=logical*', 'testSuite' ); interceptURL( @@ -523,9 +516,9 @@ describe( visitTestSuiteDetailsPage(NEW_TEST_SUITE.name); - addOwner(OWNER1); updateOwner(OWNER2); removeOwner(OWNER2); + updateOwner(OWNER1); }); it('Add test case to logical test suite', () => { @@ -557,7 +550,7 @@ describe( verifyResponseStatusCode('@putTestCase', 200); }); - it.skip('Remove test case from logical test suite', () => { + it('Remove test case from logical test suite', () => { interceptURL('GET', '/api/v1/dataQuality/testCases?fields=*', 'testCase'); interceptURL( 'GET', @@ -582,7 +575,44 @@ describe( verifyResponseStatusCode('@removeTestCase', 200); }); - it.skip('Delete test suite', () => { + it('Test suite filters', () => { + interceptURL( + 'GET', + '/api/v1/dataQuality/testSuites/search/list?*testSuiteType=logical*', + 'testSuite' + ); + interceptURL( + 'GET', + '/api/v1/dataQuality/testSuites/search/list?*owner=*', + 'testSuiteByOwner' + ); + cy.sidebarClick(SidebarItem.DATA_QUALITY); + + cy.get('[data-testid="by-test-suites"]').click(); + verifyResponseStatusCode('@testSuite', 200); + + // owner filter + cy.get('[data-testid="owner-select-filter"]').click(); + cy.get("[data-testid='select-owner-tabs']").should('be.visible'); + cy.get('.ant-tabs [id*=tab-users]').click(); + + interceptURL( + 'GET', + `api/v1/search/query?q=*&index=user_search_index*`, + 'searchOwner' + ); + + cy.get('[data-testid="owner-select-users-search-bar"]').type(OWNER1); + + verifyResponseStatusCode('@searchOwner', 200); + cy.get(`.ant-popover [title="${OWNER1}"]`).click(); + verifyResponseStatusCode('@testSuiteByOwner', 200); + cy.get(`[data-testid="${NEW_TEST_SUITE.name}"]`) + .scrollIntoView() + .should('be.visible'); + }); + + it('Delete test suite', () => { visitTestSuiteDetailsPage(NEW_TEST_SUITE.name); cy.get('[data-testid="manage-button"]').should('be.visible').click(); @@ -613,7 +643,7 @@ describe( .click(); verifyResponseStatusCode('@deleteTestSuite', 200); - toastNotification('Test Suite deleted successfully!'); + toastNotification('"mysql_matrix" deleted successfully!'); }); it('delete created service', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/DataQuality.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/DataQuality.interface.ts index ad601b25c68..29b64b34300 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/DataQuality.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/DataQuality.interface.ts @@ -14,4 +14,5 @@ export type DataQualitySearchParams = { searchValue: string; status: string; type: string; + owner: string; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx index 1a7862b72e1..c2ced3d73c7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx @@ -10,10 +10,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, Col, Row } from 'antd'; +import { Button, Col, Form, Row, Select, Space } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; -import { isString } from 'lodash'; +import { isEmpty } from 'lodash'; import QueryString from 'qs'; import React, { ReactNode, @@ -23,11 +23,18 @@ import React, { useState, } from 'react'; import { useTranslation } from 'react-i18next'; -import { Link, useParams } from 'react-router-dom'; -import { getEntityDetailsPath, ROUTES } from '../../../../constants/constants'; +import { Link, useHistory, useLocation, useParams } from 'react-router-dom'; +import { + getEntityDetailsPath, + INITIAL_PAGING_VALUE, + ROUTES, +} from '../../../../constants/constants'; import { PROGRESS_BAR_COLOR } from '../../../../constants/TestSuite.constant'; import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider'; -import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum'; +import { + ERROR_PLACEHOLDER_TYPE, + SORT_ORDER, +} from '../../../../enums/common.enum'; import { EntityTabs, EntityType } from '../../../../enums/entity.enum'; import { TestSummary } from '../../../../generated/entity/data/table'; import { EntityReference } from '../../../../generated/entity/type'; @@ -35,8 +42,8 @@ import { TestSuite } from '../../../../generated/tests/testCase'; import { usePaging } from '../../../../hooks/paging/usePaging'; import { DataQualityPageTabs } from '../../../../pages/DataQuality/DataQualityPage.interface'; import { - getListTestSuites, - ListTestSuitePrams, + getListTestSuitesBySearch, + ListTestSuitePramsBySearch, TestSuiteType, } from '../../../../rest/testAPI'; import { getEntityName } from '../../../../utils/EntityUtils'; @@ -47,14 +54,34 @@ import FilterTablePlaceHolder from '../../../common/ErrorWithPlaceholder/FilterT import NextPrevious from '../../../common/NextPrevious/NextPrevious'; import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface'; import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component'; +import Searchbar from '../../../common/SearchBarComponent/SearchBar.component'; import Table from '../../../common/Table/Table'; +import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/UserTeamSelectableList.component'; import { TableProfilerTab } from '../../../Database/Profiler/ProfilerDashboard/profilerDashboard.interface'; import ProfilerProgressWidget from '../../../Database/Profiler/TableProfiler/ProfilerProgressWidget/ProfilerProgressWidget'; +import { DataQualitySearchParams } from '../../DataQuality.interface'; export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { const { t } = useTranslation(); const { tab = DataQualityPageTabs.TABLES } = useParams<{ tab: DataQualityPageTabs }>(); + const history = useHistory(); + const location = useLocation(); + + const params = useMemo(() => { + const search = location.search; + + const params = QueryString.parse( + search.startsWith('?') ? search.substring(1) : search + ); + + return params as DataQualitySearchParams; + }, [location]); + const { searchValue, owner } = params; + const selectedOwner = useMemo( + () => (owner ? JSON.parse(owner) : undefined), + [owner] + ); const { permissions } = usePermissionProvider(); const { testSuite: testSuitePermission } = permissions; @@ -71,6 +98,14 @@ export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { const [isLoading, setIsLoading] = useState(true); + const ownerFilterValue = useMemo(() => { + return selectedOwner + ? { + key: selectedOwner.fullyQualifiedName ?? selectedOwner.name, + label: getEntityName(selectedOwner), + } + : undefined; + }, [selectedOwner]); const columns = useMemo(() => { const data: ColumnsType = [ { @@ -153,17 +188,27 @@ export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { return data; }, []); - const fetchTestSuites = async (params?: ListTestSuitePrams) => { + const fetchTestSuites = async ( + currentPage = INITIAL_PAGING_VALUE, + params?: ListTestSuitePramsBySearch + ) => { setIsLoading(true); try { - const result = await getListTestSuites({ + const result = await getListTestSuitesBySearch({ ...params, fields: 'owner,summary', - includeEmptyTestSuites: !(tab === DataQualityPageTabs.TABLES), + q: searchValue ? `*${searchValue}*` : undefined, + owner: ownerFilterValue?.key, + offset: (currentPage - 1) * pageSize, + includeEmptyTestSuites: tab !== DataQualityPageTabs.TABLES, testSuiteType: tab === DataQualityPageTabs.TABLES ? TestSuiteType.executable : TestSuiteType.logical, + sortField: 'testCaseResultSummary.timestamp', + sortType: SORT_ORDER.DESC, + sortNestedPath: 'testCaseResultSummary', + sortNestedMode: ['max'], }); setTestSuites(result.data); handlePagingChange(result.paging); @@ -175,25 +220,38 @@ export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { }; const handleTestSuitesPageChange = useCallback( - ({ cursorType, currentPage }: PagingHandlerParams) => { - if (isString(cursorType)) { - fetchTestSuites({ - [cursorType]: paging?.[cursorType], - limit: pageSize, - }); - } + ({ currentPage }: PagingHandlerParams) => { + fetchTestSuites(currentPage, { limit: pageSize }); handlePageChange(currentPage); }, [pageSize, paging] ); + const handleSearchParam = ( + value: string, + key: keyof DataQualitySearchParams + ) => { + history.push({ + search: QueryString.stringify({ + ...params, + [key]: isEmpty(value) ? undefined : value, + }), + }); + }; + + const handleOwnerSelect = (owner?: EntityReference) => { + handleSearchParam(owner ? JSON.stringify(owner) : '', 'owner'); + }; + useEffect(() => { if (testSuitePermission?.ViewAll || testSuitePermission?.ViewBasic) { - fetchTestSuites({ limit: pageSize }); + fetchTestSuites(INITIAL_PAGING_VALUE, { + limit: pageSize, + }); } else { setIsLoading(false); } - }, [testSuitePermission, pageSize]); + }, [testSuitePermission, pageSize, searchValue, owner]); if (!testSuitePermission?.ViewAll && !testSuitePermission?.ViewBasic) { return ; @@ -201,11 +259,45 @@ export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { return ( - + + +
+ + + + handleSearchParam(value, 'searchValue') + } + /> + + + +