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 bfe347ff878..b2643e7d1f8 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 @@ -1057,6 +1057,7 @@ describe( }); cy.get('[value="serviceName"]').click({ waitForAnimations: true }); verifyResponseStatusCode('@getTestCase', 200); + cy.get('#serviceName').should('not.exist'); // Test case filter by Tags interceptURL( @@ -1073,12 +1074,13 @@ describe( verifyResponseStatusCode('@getTestCaseByTags', 200); verifyFilterTestCase(); verifyFilter2TestCase(true); - // remove service filter + // remove tags filter cy.get('[data-testid="advanced-filter"]').click({ waitForAnimations: true, }); cy.get('[value="tags"]').click({ waitForAnimations: true }); verifyResponseStatusCode('@getTestCase', 200); + cy.get('#tags').should('not.exist'); // Test case filter by Tier interceptURL( @@ -1094,12 +1096,13 @@ describe( verifyResponseStatusCode('@getTestCaseByTier', 200); verifyFilterTestCase(); verifyFilter2TestCase(true); - // remove service filter + // remove tier filter cy.get('[data-testid="advanced-filter"]').click({ waitForAnimations: true, }); cy.get('[value="tier"]').click({ waitForAnimations: true }); verifyResponseStatusCode('@getTestCase', 200); + cy.get('#tier').should('not.exist'); // Test case filter by table name interceptURL( @@ -1196,6 +1199,25 @@ describe( verifyResponseStatusCode('@testCasePlatformByOpenMetadata', 200); cy.clickOutside(); verifyFilterTestCase(); + cy.url().then((url) => { + cy.reload(); + verifyResponseStatusCode('@testCasePlatformByOpenMetadata', 200); + cy.url().then((updatedUrl) => { + expect(url).to.be.equal(updatedUrl); + }); + }); + + cy.get('[data-testid="advanced-filter"]').click({ + waitForAnimations: true, + }); + cy.get('[value="testPlatforms"]').click({ + waitForAnimations: true, + }); + verifyResponseStatusCode('@getTestCase', 200); + cy.get('[value="platform-select-filter"]').should('not.exist'); + cy.reload(); + verifyResponseStatusCode('@getTestCase', 200); + cy.get('[value="tier"]').should('not.exist'); }); it('Filter with domain', () => { 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 29b64b34300..f4a41b58727 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 @@ -1,3 +1,8 @@ +import { DateRangeObject } from 'Models'; +import { TestCaseStatus } from '../../generated/tests/testCase'; +import { TestPlatform } from '../../generated/tests/testDefinition'; +import { TestCaseType } from '../../rest/testAPI'; + /* * Copyright 2023 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -10,9 +15,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type DataQualitySearchParams = { +export type TestSuiteSearchParams = { searchValue: string; status: string; type: string; owner: string; }; + +export type TestCaseSearchParams = { + searchValue?: string; + tableFqn?: string; + testPlatforms?: TestPlatform[]; + testCaseStatus?: TestCaseStatus; + testCaseType?: TestCaseType; + lastRunRange?: DateRangeObject; + tier?: string; + tags?: string; + serviceName?: string; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx index f54d680bcad..6d4d785170f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx @@ -30,11 +30,9 @@ import { debounce, entries, isEmpty, - isEqual, isUndefined, - omit, - omitBy, startCase, + uniq, } from 'lodash'; import QueryString from 'qs'; import React, { @@ -67,14 +65,10 @@ import { usePaging } from '../../../hooks/paging/usePaging'; import { DataQualityPageTabs } from '../../../pages/DataQuality/DataQualityPage.interface'; import { searchQuery } from '../../../rest/searchAPI'; import { getTags } from '../../../rest/tagAPI'; -import { - getListTestCaseBySearch, - ListTestCaseParamsBySearch, -} from '../../../rest/testAPI'; -import { buildTestCaseParams } from '../../../utils/DataQuality/DataQualityUtils'; +import { getListTestCaseBySearch } from '../../../rest/testAPI'; +import { getTestCaseFiltersValue } from '../../../utils/DataQuality/DataQualityUtils'; import { getEntityName } from '../../../utils/EntityUtils'; import { getDataQualityPagePath } from '../../../utils/RouterUtils'; -import { generateEntityLink } from '../../../utils/TableUtils'; import tagClassBase from '../../../utils/TagClassBase'; import { showErrorToast } from '../../../utils/ToastUtils'; import DatePickerMenu from '../../common/DatePickerMenu/DatePickerMenu.component'; @@ -82,7 +76,7 @@ import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface'; import Searchbar from '../../common/SearchBarComponent/SearchBar.component'; import DataQualityTab from '../../Database/Profiler/DataQualityTab/DataQualityTab'; -import { DataQualitySearchParams } from '../DataQuality.interface'; +import { TestCaseSearchParams } from '../DataQuality.interface'; export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { const [form] = useForm(); @@ -105,13 +99,12 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { search.startsWith('?') ? search.substring(1) : search ); - return params as DataQualitySearchParams; - }, [location]); + return params as TestCaseSearchParams; + }, [location.search]); const { searchValue = '' } = params; const [testCase, setTestCase] = useState([]); const [isLoading, setIsLoading] = useState(true); - const [filters, setFilters] = useState({}); const [selectedFilter, setSelectedFilter] = useState([ TEST_CASE_FILTERS.status, TEST_CASE_FILTERS.type, @@ -127,12 +120,12 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { showPagination, } = usePaging(PAGE_SIZE); - const handleSearchParam = ( - value: string | boolean, - key: keyof DataQualitySearchParams + const handleSearchParam = ( + key: K, + value?: TestCaseSearchParams[K] ) => { history.push({ - search: QueryString.stringify({ ...params, [key]: value }), + search: QueryString.stringify({ ...params, [key]: value || undefined }), }); }; @@ -150,12 +143,17 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { const fetchTestCases = async ( currentPage = INITIAL_PAGING_VALUE, - params?: ListTestCaseParamsBySearch + filters?: string[] ) => { + const updatedParams = getTestCaseFiltersValue( + params, + filters ?? selectedFilter + ); + setIsLoading(true); try { const { data, paging } = await getListTestCaseBySearch({ - ...params, + ...updatedParams, testCaseStatus: isEmpty(params?.testCaseStatus) ? undefined : params?.testCaseStatus, @@ -192,31 +190,16 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { }; const handlePagingClick = ({ currentPage }: PagingHandlerParams) => { - fetchTestCases(currentPage, filters); + fetchTestCases(currentPage); }; - const handleFilterChange: FormProps['onValuesChange'] = (_, values) => { - const { lastRunRange, tableFqn } = values; - const startTimestamp = lastRunRange?.startTs; - const endTimestamp = lastRunRange?.endTs; - const entityLink = tableFqn ? generateEntityLink(tableFqn) : undefined; - const params = { - ...omit(values, ['lastRunRange', 'tableFqn']), - startTimestamp, - endTimestamp, - entityLink, + const handleFilterChange: FormProps['onValuesChange'] = + (value?: TestCaseSearchParams) => { + if (!isUndefined(value)) { + const [data] = Object.entries(value); + handleSearchParam(data[0] as keyof TestCaseSearchParams, data[1]); + } }; - const updatedParams = omitBy( - buildTestCaseParams(params, selectedFilter), - isUndefined - ); - - if (!isEqual(filters, updatedParams)) { - fetchTestCases(INITIAL_PAGING_VALUE, updatedParams); - } - - setFilters(updatedParams); - }; const fetchTierOptions = async () => { try { @@ -360,33 +343,46 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { } }; + const getInitialOptions = (key: string, isLengthCheck = false) => { + switch (key) { + case TEST_CASE_FILTERS.tier: + (isEmpty(tierOptions) || !isLengthCheck) && fetchTierOptions(); + + break; + case TEST_CASE_FILTERS.table: + (isEmpty(tableOptions) || !isLengthCheck) && fetchTableData(); + + break; + case TEST_CASE_FILTERS.tags: + (isEmpty(tagOptions) || !isLengthCheck) && fetchTagOptions(); + + break; + case TEST_CASE_FILTERS.service: + (isEmpty(serviceOptions) || !isLengthCheck) && fetchServiceOptions(); + + break; + + default: + break; + } + }; + const handleMenuClick = ({ key }: { key: string }) => { setSelectedFilter((prevSelected) => { if (prevSelected.includes(key)) { const updatedValue = prevSelected.filter( (selected) => selected !== key ); - const updatedFilters = omitBy( - buildTestCaseParams(filters, updatedValue), - isUndefined - ); form.setFieldsValue({ [key]: undefined }); - if (!isEqual(filters, updatedFilters)) { - fetchTestCases(INITIAL_PAGING_VALUE, updatedFilters); - } - setFilters(updatedFilters); return updatedValue; } - return [...prevSelected, key]; + return uniq([...prevSelected, key]); }); - // Fetch options based on the selected filter - key === TEST_CASE_FILTERS.tier && fetchTierOptions(); - key === TEST_CASE_FILTERS.tags && fetchTagOptions(); - key === TEST_CASE_FILTERS.table && fetchTableData(); - key === TEST_CASE_FILTERS.service && fetchServiceOptions(); + getInitialOptions(key); + handleSearchParam(key as keyof TestCaseSearchParams, undefined); }; const filterMenu: ItemType[] = useMemo(() => { @@ -396,7 +392,7 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { value: filter, onClick: handleMenuClick, })); - }, [filters]); + }, []); const debounceFetchTableData = useCallback(debounce(fetchTableData, 1000), [ fetchTableData, @@ -411,15 +407,30 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { [fetchServiceOptions] ); - useEffect(() => { - if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) { - if (tab === DataQualityPageTabs.TEST_CASES) { - fetchTestCases(INITIAL_PAGING_VALUE, filters); + const getTestCases = () => { + if (!isEmpty(params)) { + const updatedValue = uniq([...selectedFilter, ...Object.keys(params)]); + for (const key of updatedValue) { + getInitialOptions(key, true); } + setSelectedFilter(updatedValue); + fetchTestCases(INITIAL_PAGING_VALUE, updatedValue); + form.setFieldsValue(params); + } else { + fetchTestCases(INITIAL_PAGING_VALUE); + } + }; + + useEffect(() => { + if ( + (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) && + tab === DataQualityPageTabs.TEST_CASES + ) { + getTestCases(); } else { setIsLoading(false); } - }, [tab, searchValue, testCasePermission, pageSize]); + }, [tab, testCasePermission, pageSize, params]); const pagingData = useMemo( () => ({ @@ -443,9 +454,8 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { data-testid="test-case-container" gutter={[16, 16]}> -
form={form} - initialValues={filters} layout="horizontal" onValuesChange={handleFilterChange}> @@ -456,7 +466,7 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => { entity: t('label.test-case-lowercase'), })} searchValue={searchValue} - onSearch={(value) => handleSearchParam(value, 'searchValue')} + onSearch={(value) => handleSearchParam('searchValue', value)} /> 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 924d9c2713c..de106db9333 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 @@ -58,7 +58,7 @@ 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'; +import { TestSuiteSearchParams } from '../../DataQuality.interface'; export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { const { t } = useTranslation(); @@ -74,7 +74,7 @@ export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { search.startsWith('?') ? search.substring(1) : search ); - return params as DataQualitySearchParams; + return params as TestSuiteSearchParams; }, [location]); const { searchValue, owner } = params; const selectedOwner = useMemo( @@ -228,7 +228,7 @@ export const TestSuites = ({ summaryPanel }: { summaryPanel: ReactNode }) => { const handleSearchParam = ( value: string, - key: keyof DataQualitySearchParams + key: keyof TestSuiteSearchParams ) => { history.push({ search: QueryString.stringify({ diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts b/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts index 44e5831a0a2..df0705cdbc3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/profiler.constant.ts @@ -14,6 +14,7 @@ import { t } from 'i18next'; import { capitalize, map, startCase, values } from 'lodash'; import { DateFilterType, StepperStepType } from 'Models'; +import { TestCaseSearchParams } from '../components/DataQuality/DataQuality.interface'; import { CSMode } from '../enums/codemirror.enum'; import { DMLOperationType } from '../generated/api/data/createTableProfile'; import { @@ -417,7 +418,7 @@ export const TEST_CASE_STATUS_OPTION = [ })), ]; -export const TEST_CASE_FILTERS = { +export const TEST_CASE_FILTERS: Record = { table: 'tableFqn', platform: 'testPlatforms', type: 'testCaseType', diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.test.ts index a8e13cc2b99..b9e3471a4a4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.test.ts @@ -10,6 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { TestCaseSearchParams } from '../../components/DataQuality/DataQuality.interface'; import { TestDataType, TestDefinition, @@ -18,162 +19,279 @@ import { ListTestCaseParamsBySearch } from '../../rest/testAPI'; import { buildTestCaseParams, createTestCaseParameters, + getTestCaseFiltersValue, } from './DataQualityUtils'; jest.mock('../../constants/profiler.constant', () => ({ TEST_CASE_FILTERS: { - lastRun: 'lastRun', - table: 'table', - platform: 'platform', - type: 'type', - status: 'status', + table: 'tableFqn', + platform: 'testPlatforms', + type: 'testCaseType', + status: 'testCaseStatus', + lastRun: 'lastRunRange', + tier: 'tier', + tags: 'tags', + service: 'serviceName', }, })); -describe('buildTestCaseParams', () => { - it('should return an empty object if params is undefined', () => { - const params = undefined; - const filters = ['lastRun', 'table']; +jest.mock('../TableUtils', () => ({ + generateEntityLink: jest.fn().mockImplementation((fqn: string) => { + return `<#E::table::${fqn}>`; + }), +})); - const result = buildTestCaseParams(params, filters); +describe('DataQualityUtils', () => { + describe('buildTestCaseParams', () => { + it('should return an empty object if params is undefined', () => { + const params = undefined; + const filters = ['lastRun', 'table']; - expect(result).toEqual({}); + const result = buildTestCaseParams(params, filters); + + expect(result).toEqual({}); + }); + + it('should return the updated test case parameters with the applied filters', () => { + const params = { + endTimestamp: 1234567890, + startTimestamp: 1234567890, + entityLink: 'table1', + testPlatforms: ['DBT'], + } as ListTestCaseParamsBySearch; + const filters = ['lastRunRange', 'tableFqn']; + + const result = buildTestCaseParams(params, filters); + + expect(result).toEqual({ + endTimestamp: 1234567890, + startTimestamp: 1234567890, + entityLink: 'table1', + }); + }); }); - it('should return the updated test case parameters with the applied filters', () => { - const params = { - endTimestamp: 1234567890, - startTimestamp: 1234567890, - entityLink: 'table1', - testPlatforms: ['DBT'], - } as ListTestCaseParamsBySearch; - const filters = ['lastRun', 'table']; + describe('createTestCaseParameters', () => { + const mockDefinition = { + parameterDefinition: [ + { name: 'arrayParam', dataType: TestDataType.Array }, + { name: 'stringParam', dataType: TestDataType.String }, + ], + } as TestDefinition; - const result = buildTestCaseParams(params, filters); + it('should return an empty array for empty parameters', () => { + const result = createTestCaseParameters({}, mockDefinition); - expect(result).toEqual({ - endTimestamp: 1234567890, - startTimestamp: 1234567890, - entityLink: 'table1', + expect(result).toEqual([]); + }); + + it('should handle parameters not matching any definition', () => { + const params = { unrelatedParam: 'value' }; + const result = createTestCaseParameters(params, mockDefinition); + + expect(result).toEqual([{ name: 'unrelatedParam', value: 'value' }]); + }); + + it('should handle a mix of string and array parameters', () => { + const params = { + stringParam: 'stringValue', + arrayParam: [{ value: 'arrayValue1' }, { value: 'arrayValue2' }], + }; + const expected = [ + { name: 'stringParam', value: 'stringValue' }, + { + name: 'arrayParam', + value: JSON.stringify(['arrayValue1', 'arrayValue2']), + }, + ]; + const result = createTestCaseParameters(params, mockDefinition); + + expect(result).toEqual(expected); + }); + + it('should ignore array items without a value', () => { + const params = { + arrayParam: [{ value: '' }, { value: 'validValue' }], + }; + const expected = [ + { name: 'arrayParam', value: JSON.stringify(['validValue']) }, + ]; + const result = createTestCaseParameters(params, mockDefinition); + + expect(result).toEqual(expected); + }); + + it('should return undefined if params not present', () => { + const result = createTestCaseParameters(undefined, undefined); + + expect(result).toBeUndefined(); + }); + + it('should return params value for sqlExpression test defination type', () => { + const data = { + params: { + sqlExpression: 'select * from dim_address', + strategy: 'COUNT', + threshold: '12', + }, + selectedDefinition: { + parameterDefinition: [ + { + name: 'sqlExpression', + displayName: 'SQL Expression', + dataType: 'STRING', + description: 'SQL expression to run against the table', + required: true, + optionValues: [], + }, + { + name: 'strategy', + displayName: 'Strategy', + dataType: 'ARRAY', + description: + 'Strategy to use to run the custom SQL query (i.e. `SELECT COUNT()` or `SELECT (defaults to ROWS)', + required: false, + optionValues: ['ROWS', 'COUNT'], + }, + { + name: 'threshold', + displayName: 'Threshold', + dataType: 'NUMBER', + description: + 'Threshold to use to determine if the test passes or fails (defaults to 0).', + required: false, + optionValues: [], + }, + ], + } as TestDefinition, + }; + + const expected = [ + { + name: 'sqlExpression', + value: 'select * from dim_address', + }, + { + name: 'strategy', + value: 'COUNT', + }, + { + name: 'threshold', + value: '12', + }, + ]; + + const result = createTestCaseParameters( + data.params, + data.selectedDefinition + ); + + expect(result).toEqual(expected); + }); + }); + + describe('getTestCaseFiltersValue', () => { + const testCaseSearchParams = { + testCaseType: 'column', + testCaseStatus: 'Success', + searchValue: 'between', + testPlatforms: ['DBT', 'Deequ'], + lastRunRange: { + startTs: '1720417883445', + endTs: '1721022683445', + key: 'last7days', + title: 'Last 7 days', + }, + tags: ['PII.None'], + tier: 'Tier.Tier1', + serviceName: 'sample_data', + tableFqn: 'sample_data.ecommerce_db.shopify.fact_sale', + } as unknown as TestCaseSearchParams; + + it('should correctly process values and selectedFilter', () => { + const selectedFilter = [ + 'testCaseStatus', + 'testCaseType', + 'searchValue', + 'testPlatforms', + 'lastRunRange', + 'tags', + 'tier', + 'serviceName', + 'tableFqn', + ]; + + const expected = { + endTimestamp: '1721022683445', + startTimestamp: '1720417883445', + entityLink: '<#E::table::sample_data.ecommerce_db.shopify.fact_sale>', + testPlatforms: ['DBT', 'Deequ'], + testCaseType: 'column', + testCaseStatus: 'Success', + tags: ['PII.None'], + tier: 'Tier.Tier1', + serviceName: 'sample_data', + }; + + const result = getTestCaseFiltersValue( + testCaseSearchParams, + selectedFilter + ); + + expect(result).toEqual(expected); + }); + + it('should only create params based on selected filters', () => { + const selectedFilter = ['testPlatforms', 'lastRunRange']; + + const expected = { + testPlatforms: ['DBT', 'Deequ'], + endTimestamp: '1721022683445', + startTimestamp: '1720417883445', + }; + + const result = getTestCaseFiltersValue( + testCaseSearchParams, + selectedFilter + ); + + expect(result).toEqual(expected); + }); + + it('should only create params based on selected filters and available data', () => { + const testCaseSearchParams = { + testCaseType: 'column', + testCaseStatus: 'Success', + searchValue: 'between', + testPlatforms: ['DBT', 'Deequ'], + tags: ['PII.None'], + tier: 'Tier.Tier1', + } as unknown as TestCaseSearchParams; + const selectedFilter = ['testPlatforms', 'tags', 'testCaseStatus']; + + const expected = { + testPlatforms: ['DBT', 'Deequ'], + tags: ['PII.None'], + testCaseStatus: 'Success', + }; + + const result = getTestCaseFiltersValue( + testCaseSearchParams, + selectedFilter + ); + + expect(result).toEqual(expected); + }); + + it('should not send params if selected filter is empty', () => { + const selectedFilter: string[] = []; + + const result = getTestCaseFiltersValue( + testCaseSearchParams, + selectedFilter + ); + + expect(result).toEqual({}); }); }); }); - -describe('createTestCaseParameters', () => { - const mockDefinition = { - parameterDefinition: [ - { name: 'arrayParam', dataType: TestDataType.Array }, - { name: 'stringParam', dataType: TestDataType.String }, - ], - } as TestDefinition; - - it('should return an empty array for empty parameters', () => { - const result = createTestCaseParameters({}, mockDefinition); - - expect(result).toEqual([]); - }); - - it('should handle parameters not matching any definition', () => { - const params = { unrelatedParam: 'value' }; - const result = createTestCaseParameters(params, mockDefinition); - - expect(result).toEqual([{ name: 'unrelatedParam', value: 'value' }]); - }); - - it('should handle a mix of string and array parameters', () => { - const params = { - stringParam: 'stringValue', - arrayParam: [{ value: 'arrayValue1' }, { value: 'arrayValue2' }], - }; - const expected = [ - { name: 'stringParam', value: 'stringValue' }, - { - name: 'arrayParam', - value: JSON.stringify(['arrayValue1', 'arrayValue2']), - }, - ]; - const result = createTestCaseParameters(params, mockDefinition); - - expect(result).toEqual(expected); - }); - - it('should ignore array items without a value', () => { - const params = { - arrayParam: [{ value: '' }, { value: 'validValue' }], - }; - const expected = [ - { name: 'arrayParam', value: JSON.stringify(['validValue']) }, - ]; - const result = createTestCaseParameters(params, mockDefinition); - - expect(result).toEqual(expected); - }); - - it('should return undefined if params not present', () => { - const result = createTestCaseParameters(undefined, undefined); - - expect(result).toBeUndefined(); - }); - - it('should return params value for sqlExpression test defination type', () => { - const data = { - params: { - sqlExpression: 'select * from dim_address', - strategy: 'COUNT', - threshold: '12', - }, - selectedDefinition: { - parameterDefinition: [ - { - name: 'sqlExpression', - displayName: 'SQL Expression', - dataType: 'STRING', - description: 'SQL expression to run against the table', - required: true, - optionValues: [], - }, - { - name: 'strategy', - displayName: 'Strategy', - dataType: 'ARRAY', - description: - 'Strategy to use to run the custom SQL query (i.e. `SELECT COUNT()` or `SELECT (defaults to ROWS)', - required: false, - optionValues: ['ROWS', 'COUNT'], - }, - { - name: 'threshold', - displayName: 'Threshold', - dataType: 'NUMBER', - description: - 'Threshold to use to determine if the test passes or fails (defaults to 0).', - required: false, - optionValues: [], - }, - ], - } as TestDefinition, - }; - - const expected = [ - { - name: 'sqlExpression', - value: 'select * from dim_address', - }, - { - name: 'strategy', - value: 'COUNT', - }, - { - name: 'threshold', - value: '12', - }, - ]; - - const result = createTestCaseParameters( - data.params, - data.selectedDefinition - ); - - expect(result).toEqual(expected); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.ts index f1a28c7544b..62af24c1dba 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataQuality/DataQualityUtils.ts @@ -10,7 +10,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { isArray } from 'lodash'; +import { isArray, isUndefined, omit, omitBy } from 'lodash'; +import { TestCaseSearchParams } from '../../components/DataQuality/DataQuality.interface'; import { TEST_CASE_FILTERS } from '../../constants/profiler.constant'; import { TestCaseParameterValue } from '../../generated/tests/testCase'; import { @@ -18,6 +19,7 @@ import { TestDefinition, } from '../../generated/tests/testDefinition'; import { ListTestCaseParamsBySearch } from '../../rest/testAPI'; +import { generateEntityLink } from '../TableUtils'; /** * Builds the parameters for a test case search based on the given filters. @@ -74,3 +76,27 @@ export const createTestCaseParameters = ( }, [] as TestCaseParameterValue[]) : params; }; + +export const getTestCaseFiltersValue = ( + values: TestCaseSearchParams, + selectedFilter: string[] +) => { + const { lastRunRange, tableFqn } = values; + const startTimestamp = lastRunRange?.startTs; + const endTimestamp = lastRunRange?.endTs; + const entityLink = tableFqn ? generateEntityLink(tableFqn) : undefined; + + const apiParams = { + ...omit(values, ['lastRunRange', 'tableFqn', 'searchValue']), + startTimestamp, + endTimestamp, + entityLink, + }; + + const updatedParams = omitBy( + buildTestCaseParams(apiParams, selectedFilter), + isUndefined + ); + + return updatedParams; +};