#14941 Filtering and sorting of data quality tests & suites at Data Quality page (#15725)

* #14941 Filtering and sorting of data quality tests & suites at Data Quality page

* added filters for test case type, status and startTs and endTs

* added platform filter

* sync-translation

* added table filter

* updated pagination for filter

* fixed failing unit test

* added unit test for filters

* added cypress for test case filters

* skipping filter cypress

* skipping cypress test
This commit is contained in:
Shailesh Parmar 2024-04-09 17:05:09 +05:30 committed by GitHub
parent d9a7ebe5e1
commit 7264773d6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 656 additions and 217 deletions

View File

@ -0,0 +1,246 @@
/*
* Copyright 2024 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 { uuid } from '../../constants/constants';
import { EntityType } from '../../constants/Entity.interface';
import { DATABASE_SERVICE } from '../../constants/EntityConstant';
import { interceptURL, verifyResponseStatusCode } from '../common';
import { generateRandomTable } from '../EntityUtils';
import { visitEntityDetailsPage } from './Entity';
const tableFqn = `${DATABASE_SERVICE.entity.databaseSchema}.${DATABASE_SERVICE.entity.name}`;
const testSuite = {
name: `${tableFqn}.testSuite`,
executableEntityReference: tableFqn,
};
const testCase1 = {
name: `user_tokens_table_column_name_to_exist_${uuid()}`,
entityLink: `<#E::table::${testSuite.executableEntityReference}>`,
parameterValues: [{ name: 'columnName', value: 'id' }],
testDefinition: 'tableColumnNameToExist',
description: 'test case description',
testSuite: testSuite.name,
};
const testCase2 = {
name: `email_column_values_to_be_in_set_${uuid()}`,
entityLink: `<#E::table::${testSuite.executableEntityReference}::columns::email>`,
parameterValues: [
{ name: 'allowedValues', value: '["gmail","yahoo","collate"]' },
],
testDefinition: 'columnValuesToBeInSet',
testSuite: testSuite.name,
};
const filterTable = generateRandomTable();
const filterTableFqn = `${filterTable.databaseSchema}.${filterTable.name}`;
const filterTableTestSuite = {
name: `${filterTableFqn}.testSuite`,
executableEntityReference: filterTableFqn,
};
const testCases = [
`cy_first_table_column_count_to_be_between_${uuid()}`,
`cy_second_table_column_count_to_be_between_${uuid()}`,
`cy_third_table_column_count_to_be_between_${uuid()}`,
];
export const DATA_QUALITY_TEST_CASE_DATA = {
testCase1,
testCase2,
filterTable,
filterTableTestCases: testCases,
};
const verifyPipelineSuccessStatus = (time = 20000) => {
const newTime = time / 2;
interceptURL('GET', '/api/v1/tables/name/*?fields=testSuite*', 'testSuite');
interceptURL(
'GET',
'/api/v1/services/ingestionPipelines/*/pipelineStatus?startTs=*&endTs=*',
'pipelineStatus'
);
cy.wait(time);
cy.reload();
verifyResponseStatusCode('@testSuite', 200);
cy.get('[id*="tab-pipeline"]').click();
verifyResponseStatusCode('@pipelineStatus', 200);
cy.get('[data-testid="pipeline-status"]').then(($el) => {
const text = $el.text();
if (text !== 'Success' && text !== 'Failed' && newTime > 0) {
verifyPipelineSuccessStatus(newTime);
} else {
cy.get('[data-testid="pipeline-status"]').should('contain', 'Success');
}
});
};
export const triggerTestCasePipeline = ({
serviceName,
tableName,
}: {
serviceName: string;
tableName: string;
}) => {
interceptURL('GET', `/api/v1/tables/*/systemProfile?*`, 'systemProfile');
interceptURL('GET', `/api/v1/tables/*/tableProfile?*`, 'tableProfile');
interceptURL(
'GET',
`api/v1/tables/name/${serviceName}.*.${tableName}?fields=*&include=all`,
'waitForPageLoad'
);
visitEntityDetailsPage({
term: tableName,
serviceName: serviceName,
entity: EntityType.Table,
});
verifyResponseStatusCode('@waitForPageLoad', 200);
cy.get('[data-testid="profiler"]').should('be.visible').click();
interceptURL(
'GET',
`api/v1/tables/name/${serviceName}.*.${tableName}?include=all`,
'addTableTestPage'
);
verifyResponseStatusCode('@systemProfile', 200);
verifyResponseStatusCode('@tableProfile', 200);
interceptURL('GET', '/api/v1/dataQuality/testCases?fields=*', 'testCase');
cy.get('[data-testid="profiler-tab-left-panel"]')
.contains('Data Quality')
.click();
verifyResponseStatusCode('@testCase', 200);
interceptURL(
'GET',
'/api/v1/services/ingestionPipelines/*/pipelineStatus?startTs=*&endTs=*',
'getPipelineStatus'
);
interceptURL(
'POST',
'/api/v1/services/ingestionPipelines/trigger/*',
'triggerPipeline'
);
cy.get('[id*="tab-pipeline"]').click();
verifyResponseStatusCode('@getPipelineStatus', 200);
cy.get('[data-testid="run"]').click();
cy.wait('@triggerPipeline');
verifyPipelineSuccessStatus();
};
export const prepareDataQualityTestCases = (token: string) => {
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testSuites/executable`,
headers: { Authorization: `Bearer ${token}` },
body: testSuite,
}).then((testSuiteResponse) => {
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testCases`,
headers: { Authorization: `Bearer ${token}` },
body: testCase1,
});
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testCases`,
headers: { Authorization: `Bearer ${token}` },
body: testCase2,
});
cy.request({
method: 'POST',
url: `/api/v1/services/ingestionPipelines`,
headers: { Authorization: `Bearer ${token}` },
body: {
airflowConfig: {},
name: `${testSuite.executableEntityReference}_test_suite`,
pipelineType: 'TestSuite',
service: {
id: testSuiteResponse.body.id,
type: 'testSuite',
},
sourceConfig: {
config: {
type: 'TestSuite',
entityFullyQualifiedName: testSuite.executableEntityReference,
},
},
},
}).then((response) =>
cy.request({
method: 'POST',
url: `/api/v1/services/ingestionPipelines/deploy/${response.body.id}`,
headers: { Authorization: `Bearer ${token}` },
})
);
});
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testSuites/executable`,
headers: { Authorization: `Bearer ${token}` },
body: filterTableTestSuite,
}).then((testSuiteResponse) => {
// creating test case
testCases.forEach((testCase) => {
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testCases`,
headers: { Authorization: `Bearer ${token}` },
body: {
name: testCase,
entityLink: `<#E::table::${filterTableTestSuite.executableEntityReference}>`,
parameterValues: [
{ name: 'minColValue', value: 12 },
{ name: 'maxColValue', value: 24 },
],
testDefinition: 'tableColumnCountToBeBetween',
testSuite: filterTableTestSuite.name,
},
});
});
cy.request({
method: 'POST',
url: `/api/v1/services/ingestionPipelines`,
headers: { Authorization: `Bearer ${token}` },
body: {
airflowConfig: {},
name: `${filterTableTestSuite.executableEntityReference}_test_suite`,
pipelineType: 'TestSuite',
service: {
id: testSuiteResponse.body.id,
type: 'testSuite',
},
sourceConfig: {
config: {
type: 'TestSuite',
entityFullyQualifiedName:
filterTableTestSuite.executableEntityReference,
},
},
},
}).then((response) =>
cy.request({
method: 'POST',
url: `/api/v1/services/ingestionPipelines/deploy/${response.body.id}`,
headers: { Authorization: `Bearer ${token}` },
})
);
});
triggerTestCasePipeline({
serviceName: DATABASE_SERVICE.service.name,
tableName: filterTable.name,
});
};

View File

@ -438,3 +438,10 @@ export const visitDatabaseSchemaDetailsPage = ({
.contains(databaseSchemaName) .contains(databaseSchemaName)
.click(); .click();
}; };
export const selectOptionFromDropdown = (option: string) => {
cy.get('.ant-select-dropdown')
.not('.ant-select-dropdown-hidden')
.find(`[title="${option}"]`)
.click();
};

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { interceptURL, verifyResponseStatusCode } from '../../common/common'; import { interceptURL, verifyResponseStatusCode } from '../../common/common';
import { triggerTestCasePipeline } from '../../common/Utils/DataQuality';
import { import {
createEntityTableViaREST, createEntityTableViaREST,
deleteEntityViaREST, deleteEntityViaREST,
@ -49,29 +50,6 @@ const goToProfilerTab = () => {
cy.get('[data-testid="profiler"]').should('be.visible').click(); cy.get('[data-testid="profiler"]').should('be.visible').click();
}; };
const verifySuccessStatus = (time = 20000) => {
const newTime = time / 2;
interceptURL('GET', '/api/v1/tables/name/*?fields=testSuite*', 'testSuite');
interceptURL(
'GET',
'/api/v1/services/ingestionPipelines/*/pipelineStatus?startTs=*&endTs=*',
'pipelineStatus'
);
cy.wait(time);
cy.reload();
verifyResponseStatusCode('@testSuite', 200);
cy.get('[id*="tab-pipeline"]').click();
verifyResponseStatusCode('@pipelineStatus', 200);
cy.get('[data-testid="pipeline-status"]').then(($el) => {
const text = $el.text();
if (text !== 'Success' && text !== 'Failed' && newTime > 0) {
verifySuccessStatus(newTime);
} else {
cy.get('[data-testid="pipeline-status"]').should('contain', 'Success');
}
});
};
const acknowledgeTask = (testCase: string) => { const acknowledgeTask = (testCase: string) => {
goToProfilerTab(); goToProfilerTab();
@ -97,40 +75,6 @@ const acknowledgeTask = (testCase: string) => {
cy.get(`[data-testid="${testCase}-status"]`).should('contain', 'Ack'); cy.get(`[data-testid="${testCase}-status"]`).should('contain', 'Ack');
}; };
const triggerTestCasePipeline = () => {
interceptURL('GET', `/api/v1/tables/*/systemProfile?*`, 'systemProfile');
interceptURL('GET', `/api/v1/tables/*/tableProfile?*`, 'tableProfile');
goToProfilerTab();
interceptURL(
'GET',
`api/v1/tables/name/${DATABASE_SERVICE.service.name}.*.${TABLE_NAME}?include=all`,
'addTableTestPage'
);
verifyResponseStatusCode('@systemProfile', 200);
verifyResponseStatusCode('@tableProfile', 200);
interceptURL('GET', '/api/v1/dataQuality/testCases?fields=*', 'testCase');
cy.get('[data-testid="profiler-tab-left-panel"]')
.contains('Data Quality')
.click();
verifyResponseStatusCode('@testCase', 200);
interceptURL(
'GET',
'/api/v1/services/ingestionPipelines/*/pipelineStatus?startTs=*&endTs=*',
'getPipelineStatus'
);
interceptURL(
'POST',
'/api/v1/services/ingestionPipelines/trigger/*',
'triggerPipeline'
);
cy.get('[id*="tab-pipeline"]').click();
verifyResponseStatusCode('@getPipelineStatus', 200);
cy.get('[data-testid="run"]').click();
cy.wait('@triggerPipeline');
verifySuccessStatus();
};
const assignIncident = (testCaseName: string) => { const assignIncident = (testCaseName: string) => {
cy.sidebarClick(SidebarItem.INCIDENT_MANAGER); cy.sidebarClick(SidebarItem.INCIDENT_MANAGER);
cy.get(`[data-testid="test-case-${testCaseName}"]`).should('be.visible'); cy.get(`[data-testid="test-case-${testCaseName}"]`).should('be.visible');
@ -227,7 +171,10 @@ describe('Incident Manager', { tags: 'Observability' }, () => {
}); });
}); });
triggerTestCasePipeline(); triggerTestCasePipeline({
serviceName: DATABASE_SERVICE.service.name,
tableName: TABLE_NAME,
});
}); });
after(() => { after(() => {
@ -421,7 +368,10 @@ describe('Incident Manager', { tags: 'Observability' }, () => {
}); });
it('Re-run pipeline', () => { it('Re-run pipeline', () => {
triggerTestCasePipeline(); triggerTestCasePipeline({
serviceName: DATABASE_SERVICE.service.name,
tableName: TABLE_NAME,
});
}); });
it('Verify open and closed task', () => { it('Verify open and closed task', () => {
@ -480,7 +430,10 @@ describe('Incident Manager', { tags: 'Observability' }, () => {
}); });
it('Re-run pipeline', () => { it('Re-run pipeline', () => {
triggerTestCasePipeline(); triggerTestCasePipeline({
serviceName: DATABASE_SERVICE.service.name,
tableName: TABLE_NAME,
});
}); });
it("Verify incident's status on DQ page", () => { it("Verify incident's status on DQ page", () => {

View File

@ -14,13 +14,17 @@
import { import {
descriptionBox, descriptionBox,
interceptURL, interceptURL,
selectOptionFromDropdown,
toastNotification, toastNotification,
uuid,
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../../common/common'; } from '../../common/common';
import { createEntityTable, hardDeleteService } from '../../common/EntityUtils'; import { createEntityTable, hardDeleteService } from '../../common/EntityUtils';
import MysqlIngestionClass from '../../common/Services/MysqlIngestionClass'; import MysqlIngestionClass from '../../common/Services/MysqlIngestionClass';
import { searchServiceFromSettingPage } from '../../common/serviceUtils'; import { searchServiceFromSettingPage } from '../../common/serviceUtils';
import {
DATA_QUALITY_TEST_CASE_DATA,
prepareDataQualityTestCases,
} from '../../common/Utils/DataQuality';
import { visitEntityDetailsPage } from '../../common/Utils/Entity'; import { visitEntityDetailsPage } from '../../common/Utils/Entity';
import { import {
handleIngestionRetry, handleIngestionRetry,
@ -44,34 +48,10 @@ import { SERVICE_CATEGORIES } from '../../constants/service.constants';
import { GlobalSettingOptions } from '../../constants/settings.constant'; import { GlobalSettingOptions } from '../../constants/settings.constant';
const serviceName = `cypress-mysql`; const serviceName = `cypress-mysql`;
const tableFqn = `${DATABASE_SERVICE.entity.databaseSchema}.${DATABASE_SERVICE.entity.name}`;
const testSuite = {
name: `${tableFqn}.testSuite`,
executableEntityReference: tableFqn,
};
const testCase1 = {
name: `user_tokens_table_column_name_to_exist_${uuid()}`,
entityLink: `<#E::table::${testSuite.executableEntityReference}>`,
parameterValues: [{ name: 'columnName', value: 'id' }],
testDefinition: 'tableColumnNameToExist',
description: 'test case description',
testSuite: testSuite.name,
};
const testCase2 = {
name: `email_column_values_to_be_in_set_${uuid()}`,
entityLink: `<#E::table::${testSuite.executableEntityReference}::columns::email>`,
parameterValues: [
{ name: 'allowedValues', value: '["gmail","yahoo","collate"]' },
],
testDefinition: 'columnValuesToBeInSet',
testSuite: testSuite.name,
};
let testCaseId = '';
const OWNER1 = 'Aaron Johnson'; const OWNER1 = 'Aaron Johnson';
const OWNER2 = 'Cynthia Meyer'; const OWNER2 = 'Cynthia Meyer';
const { testCase1, testCase2, filterTable, filterTableTestCases } =
DATA_QUALITY_TEST_CASE_DATA;
const goToProfilerTab = () => { const goToProfilerTab = () => {
interceptURL( interceptURL(
'GET', 'GET',
@ -117,6 +97,12 @@ const visitTestSuiteDetailsPage = (testSuiteName) => {
clickOnTestSuite(testSuiteName); clickOnTestSuite(testSuiteName);
}; };
const verifyFilterTestCase = () => {
filterTableTestCases.map((testCase) => {
cy.get(`[data-testid="${testCase}"]`).scrollIntoView().should('be.visible');
});
};
describe( describe(
'Data Quality and Profiler should work properly', 'Data Quality and Profiler should work properly',
{ tags: 'Observability' }, { tags: 'Observability' },
@ -130,30 +116,10 @@ describe(
createEntityTable({ createEntityTable({
token, token,
...DATABASE_SERVICE, ...DATABASE_SERVICE,
tables: [DATABASE_SERVICE.entity], tables: [DATABASE_SERVICE.entity, filterTable],
}); });
cy.request({ prepareDataQualityTestCases(token);
method: 'POST',
url: `/api/v1/dataQuality/testSuites/executable`,
headers: { Authorization: `Bearer ${token}` },
body: testSuite,
}).then(() => {
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testCases`,
headers: { Authorization: `Bearer ${token}` },
body: testCase1,
}).then((response) => {
testCaseId = response.body.id;
});
cy.request({
method: 'POST',
url: `/api/v1/dataQuality/testCases`,
headers: { Authorization: `Bearer ${token}` },
body: testCase2,
});
});
}); });
}); });
@ -161,11 +127,6 @@ describe(
cy.login(); cy.login();
cy.getAllLocalStorage().then((data) => { cy.getAllLocalStorage().then((data) => {
const token = getToken(data); const token = getToken(data);
cy.request({
method: 'DELETE',
url: `/api/v1/dataQuality/testCases/${testCaseId}?hardDelete=true&recursive=false`,
headers: { Authorization: `Bearer ${token}` },
});
hardDeleteService({ hardDeleteService({
token, token,
serviceFqn: DATABASE_SERVICE.service.name, serviceFqn: DATABASE_SERVICE.service.name,
@ -841,8 +802,13 @@ describe(
.should('have.value', 'collate'); .should('have.value', 'collate');
}); });
it('Update displayName of test case', () => { // Skipping As backend throws error for newly created test case, unSkip once backend issue is resolved from @TeddyCr
interceptURL('GET', '/api/v1/dataQuality/testCases?*', 'getTestCase'); it.skip('Update displayName of test case', () => {
interceptURL(
'GET',
'/api/v1/dataQuality/testCases/search/list?*',
'getTestCase'
);
cy.sidebarClick(SidebarItem.DATA_QUALITY); cy.sidebarClick(SidebarItem.DATA_QUALITY);
@ -850,7 +816,7 @@ describe(
verifyResponseStatusCode('@getTestCase', 200); verifyResponseStatusCode('@getTestCase', 200);
interceptURL( interceptURL(
'GET', 'GET',
`/api/v1/search/query?q=*${testCase1.name}*&index=test_case_search_index*`, `/api/v1/dataQuality/testCases/search/list?*q=*${testCase1.name}*`,
'searchTestCase' 'searchTestCase'
); );
cy.get( cy.get(
@ -878,6 +844,119 @@ describe(
}); });
}); });
// Skipping As backend throws error for newly created test case, unSkip once backend issue is resolved from @TeddyCr
it.skip('Test case filters', () => {
interceptURL(
'GET',
'/api/v1/dataQuality/testCases/search/list?*',
'getTestCase'
);
cy.sidebarClick(SidebarItem.DATA_QUALITY);
cy.get('[data-testid="by-test-cases"]').click();
verifyResponseStatusCode('@getTestCase', 200);
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*q=*${filterTableTestCases[0]}*`,
'searchTestCase'
);
// Test case search filter
cy.get(
'[data-testid="test-case-container"] [data-testid="searchbar"]'
).type(filterTableTestCases[0]);
verifyResponseStatusCode('@searchTestCase', 200);
cy.get(`[data-testid="${filterTableTestCases[0]}"]`)
.scrollIntoView()
.should('be.visible');
cy.get('.ant-input-clear-icon').click();
verifyResponseStatusCode('@getTestCase', 200);
// Test case filter by table name
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*entityLink=*${filterTable.name}*`,
'searchTestCaseByTable'
);
cy.get('#tableFqn').scrollIntoView().type(filterTable.name);
selectOptionFromDropdown(filterTable.name);
verifyResponseStatusCode('@searchTestCaseByTable', 200);
verifyFilterTestCase();
// Test case filter by test type
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*testCaseType=column*entityLink=*${filterTable.name}*`,
'testCaseTypeByColumn'
);
cy.get('[data-testid="test-case-type-select-filter"]').click();
selectOptionFromDropdown('Column');
verifyResponseStatusCode('@testCaseTypeByColumn', 200);
cy.get('[data-testid="search-error-placeholder"]').should('be.visible');
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*testCaseType=table*entityLink=*${filterTable.name}*`,
'testCaseTypeByTable'
);
cy.get('[data-testid="test-case-type-select-filter"]').click();
selectOptionFromDropdown('Table');
verifyResponseStatusCode('@testCaseTypeByTable', 200);
verifyFilterTestCase();
cy.get('[data-testid="test-case-type-select-filter"]').click();
selectOptionFromDropdown('All');
verifyResponseStatusCode('@getTestCase', 200);
// Test case filter by status
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*testCaseStatus=Success*entityLink=*${filterTable.name}*`,
'testCaseStatusBySuccess'
);
cy.get('[data-testid="status-select-filter"]').click();
selectOptionFromDropdown('Success');
verifyResponseStatusCode('@testCaseStatusBySuccess', 200);
cy.get('[data-testid="search-error-placeholder"]').should('be.visible');
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*testCaseStatus=Failed*entityLink=*${filterTable.name}*`,
'testCaseStatusByFailed'
);
cy.get('[data-testid="status-select-filter"]').click();
selectOptionFromDropdown('Failed');
verifyResponseStatusCode('@testCaseStatusByFailed', 200);
verifyFilterTestCase();
// Test case filter by platform
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*testPlatforms=DBT*entityLink=*${filterTable.name}*`,
'testCasePlatformByDBT'
);
cy.get('[data-testid="platform-select-filter"]').click();
selectOptionFromDropdown('DBT');
verifyResponseStatusCode('@testCasePlatformByDBT', 200);
cy.clickOutside();
cy.get('[data-testid="search-error-placeholder"]').should('be.visible');
cy.get(
'[data-testid="platform-select-filter"] .ant-select-clear'
).click();
verifyResponseStatusCode('@getTestCase', 200);
interceptURL(
'GET',
`/api/v1/dataQuality/testCases/search/list?*testPlatforms=OpenMetadata*entityLink=*${filterTable.name}*`,
'testCasePlatformByOpenMetadata'
);
cy.get('[data-testid="platform-select-filter"]').click();
selectOptionFromDropdown('OpenMetadata');
verifyResponseStatusCode('@testCasePlatformByOpenMetadata', 200);
cy.clickOutside();
verifyFilterTestCase();
});
it('Update profiler setting modal', () => { it('Update profiler setting modal', () => {
const profilerSetting = { const profilerSetting = {
profileSample: '60', profileSample: '60',

View File

@ -10,29 +10,46 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Col, Row } from 'antd'; import { Col, DatePicker, Form, FormProps, Row, Select, Space } from 'antd';
import { DefaultOptionType } from 'antd/lib/select';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { debounce, isEmpty, omit } from 'lodash';
import QueryString from 'qs'; import QueryString from 'qs';
import React, { ReactNode, useEffect, useMemo, useState } from 'react'; import React, {
ReactNode,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useLocation, useParams } from 'react-router-dom'; import { useHistory, useLocation, useParams } from 'react-router-dom';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider'; import { WILD_CARD_CHAR } from '../../../constants/char.constants';
import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
import { SearchIndex } from '../../../enums/search.enum';
import { TestCase } from '../../../generated/tests/testCase';
import { usePaging } from '../../../hooks/paging/usePaging';
import { import {
SearchHitBody, INITIAL_PAGING_VALUE,
TestCaseSearchSource, PAGE_SIZE,
} from '../../../interface/search.interface'; PAGE_SIZE_BASE,
} from '../../../constants/constants';
import {
TEST_CASE_PLATFORM_OPTION,
TEST_CASE_STATUS_OPTION,
TEST_CASE_TYPE_OPTION,
} from '../../../constants/profiler.constant';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ERROR_PLACEHOLDER_TYPE, SORT_ORDER } from '../../../enums/common.enum';
import { SearchIndex } from '../../../enums/search.enum';
import { TestCase, TestCaseStatus } from '../../../generated/tests/testCase';
import { usePaging } from '../../../hooks/paging/usePaging';
import { DataQualityPageTabs } from '../../../pages/DataQuality/DataQualityPage.interface'; import { DataQualityPageTabs } from '../../../pages/DataQuality/DataQualityPage.interface';
import { searchQuery } from '../../../rest/searchAPI'; import { searchQuery } from '../../../rest/searchAPI';
import { import {
getListTestCase, getListTestCaseBySearch,
getTestCaseById, ListTestCaseParamsBySearch,
ListTestCaseParams, TestCaseType,
} from '../../../rest/testAPI'; } from '../../../rest/testAPI';
import { getEntityName } from '../../../utils/EntityUtils';
import { getDataQualityPagePath } from '../../../utils/RouterUtils'; import { getDataQualityPagePath } from '../../../utils/RouterUtils';
import { generateEntityLink } from '../../../utils/TableUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface'; import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface';
@ -47,6 +64,8 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
const { tab } = useParams<{ tab: DataQualityPageTabs }>(); const { tab } = useParams<{ tab: DataQualityPageTabs }>();
const { permissions } = usePermissionProvider(); const { permissions } = usePermissionProvider();
const { testCase: testCasePermission } = permissions; const { testCase: testCasePermission } = permissions;
const [tableOptions, setTableOptions] = useState<DefaultOptionType[]>([]);
const [isTableLoading, setIsTableLoading] = useState(false);
const params = useMemo(() => { const params = useMemo(() => {
const search = location.search; const search = location.search;
@ -61,6 +80,10 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
const [testCase, setTestCase] = useState<TestCase[]>([]); const [testCase, setTestCase] = useState<TestCase[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const [filters, setFilters] = useState<ListTestCaseParamsBySearch>({
testCaseType: TestCaseType.all,
testCaseStatus: '' as TestCaseStatus,
});
const { const {
currentPage, currentPage,
@ -70,7 +93,7 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
paging, paging,
handlePagingChange, handlePagingChange,
showPagination, showPagination,
} = usePaging(); } = usePaging(PAGE_SIZE);
const handleSearchParam = ( const handleSearchParam = (
value: string | boolean, value: string | boolean,
@ -93,17 +116,28 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
} }
}; };
const fetchTestCases = async (params?: ListTestCaseParams) => { const fetchTestCases = async (
currentPage = INITIAL_PAGING_VALUE,
params?: ListTestCaseParamsBySearch
) => {
setIsLoading(true); setIsLoading(true);
try { try {
const { data, paging } = await getListTestCase({ const { data, paging } = await getListTestCaseBySearch({
...params, ...params,
testCaseStatus: isEmpty(params?.testCaseStatus)
? undefined
: params?.testCaseStatus,
limit: pageSize, limit: pageSize,
fields: 'testDefinition,testCaseResult,testSuite,incidentId', includeAllTests: true,
orderByLastExecutionDate: true, fields: 'testCaseResult,testSuite,incidentId',
q: searchValue ? `*${searchValue}*` : undefined,
offset: (currentPage - 1) * pageSize,
sortType: SORT_ORDER.DESC,
sortField: 'testCaseResult.timestamp',
}); });
setTestCase(data); setTestCase(data);
handlePagingChange(paging); handlePagingChange(paging);
handlePageChange(currentPage);
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
@ -125,76 +159,71 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
}); });
}; };
const searchTestCases = async (page = 1) => { const handlePagingClick = ({ currentPage }: PagingHandlerParams) => {
setIsLoading(true); fetchTestCases(currentPage, filters);
};
const handleFilterChange: FormProps['onValuesChange'] = (_, values) => {
const { lastRunRange, tableFqn } = values;
const startTimestamp = lastRunRange?.[0]
? lastRunRange[0].set({ h: 0, m: 0 }).unix() * 1000
: undefined;
const endTimestamp = lastRunRange?.[1]
? lastRunRange[1].set({ h: 23, m: 59 }).unix() * 1000
: undefined;
const entityLink = tableFqn ? generateEntityLink(tableFqn) : undefined;
const params = {
...omit(values, ['lastRunRange', 'tableFqn']),
startTimestamp,
endTimestamp,
entityLink,
};
fetchTestCases(INITIAL_PAGING_VALUE, params);
setFilters((prev) => ({ ...prev, ...params }));
};
const fetchTableData = async (search = WILD_CARD_CHAR) => {
setIsTableLoading(true);
try { try {
const response = await searchQuery({ const response = await searchQuery({
pageNumber: page, query: `*${search}*`,
pageSize: pageSize, pageNumber: 1,
searchIndex: SearchIndex.TEST_CASE, pageSize: PAGE_SIZE_BASE,
query: searchValue, searchIndex: SearchIndex.TABLE,
fetchSource: false, fetchSource: true,
includeFields: ['name', 'fullyQualifiedName', 'displayName'],
}); });
const promise = (
response.hits.hits as SearchHitBody<
SearchIndex.TEST_CASE,
TestCaseSearchSource
>[]
).map((value) =>
getTestCaseById(value._id ?? '', {
fields: 'testDefinition,testCaseResult,testSuite,incidentId',
})
);
const value = await Promise.allSettled(promise); const options = response.hits.hits.map((hit) => ({
label: getEntityName(hit._source),
const testSuites = value.reduce((prev, curr) => { value: hit._source.fullyQualifiedName,
if (curr.status === 'fulfilled') { }));
return [...prev, curr.value.data]; setTableOptions(options);
}
return prev;
}, [] as TestCase[]);
setTestCase(testSuites);
handlePageChange(page);
handlePagingChange({ total: response.hits.total.value ?? 0 });
} catch (error) { } catch (error) {
setTestCase([]); setTableOptions([]);
} finally { } finally {
setIsLoading(false); setIsTableLoading(false);
} }
}; };
const handlePagingClick = ({
cursorType, const debounceFetchTableData = useCallback(debounce(fetchTableData, 1000), [
currentPage, fetchTableData,
}: PagingHandlerParams) => { ]);
if (searchValue) {
searchTestCases(currentPage);
} else {
if (cursorType) {
fetchTestCases({
[cursorType]: paging?.[cursorType],
});
}
}
handlePageChange(currentPage);
};
useEffect(() => { useEffect(() => {
if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) { if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) {
if (tab === DataQualityPageTabs.TEST_CASES) { if (tab === DataQualityPageTabs.TEST_CASES) {
if (searchValue) { fetchTestCases(INITIAL_PAGING_VALUE, filters);
searchTestCases();
} else {
fetchTestCases();
}
} }
} else { } else {
setIsLoading(false); setIsLoading(false);
} }
}, [tab, searchValue, testCasePermission, pageSize]); }, [tab, searchValue, testCasePermission, pageSize]);
useEffect(() => {
fetchTableData();
}, []);
const pagingData = useMemo( const pagingData = useMemo(
() => ({ () => ({
paging, paging,
@ -202,16 +231,9 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
pagingHandler: handlePagingClick, pagingHandler: handlePagingClick,
pageSize, pageSize,
onShowSizeChange: handlePageSizeChange, onShowSizeChange: handlePageSizeChange,
isNumberBased: Boolean(searchValue), isNumberBased: true,
}), }),
[ [paging, currentPage, handlePagingClick, pageSize, handlePageSizeChange]
paging,
currentPage,
handlePagingClick,
pageSize,
handlePageSizeChange,
searchValue,
]
); );
if (!testCasePermission?.ViewAll && !testCasePermission?.ViewBasic) { if (!testCasePermission?.ViewAll && !testCasePermission?.ViewBasic) {
@ -223,12 +245,78 @@ export const TestCases = ({ summaryPanel }: { summaryPanel: ReactNode }) => {
className="p-x-lg p-t-md" className="p-x-lg p-t-md"
data-testid="test-case-container" data-testid="test-case-container"
gutter={[16, 16]}> gutter={[16, 16]}>
<Col span={8}> <Col span={24}>
<Form
initialValues={filters}
layout="horizontal"
onValuesChange={handleFilterChange}>
<Space wrap align="center" className="w-full justify-between">
<Form.Item className="m-0 w-80">
<Searchbar <Searchbar
removeMargin removeMargin
placeholder={t('label.search-entity', {
entity: t('label.test-case-lowercase'),
})}
searchValue={searchValue} searchValue={searchValue}
onSearch={(value) => handleSearchParam(value, 'searchValue')} onSearch={(value) => handleSearchParam(value, 'searchValue')}
/> />
</Form.Item>
<Form.Item
className="m-0 w-52"
label={t('label.table')}
name="tableFqn">
<Select
allowClear
showSearch
data-testid="table-select-filter"
loading={isTableLoading}
options={tableOptions}
placeholder={t('label.table')}
onSearch={debounceFetchTableData}
/>
</Form.Item>
<Form.Item
className="m-0 w-min-20"
label={t('label.platform')}
name="testPlatforms">
<Select
allowClear
data-testid="platform-select-filter"
mode="multiple"
options={TEST_CASE_PLATFORM_OPTION}
placeholder={t('label.platform')}
/>
</Form.Item>
<Form.Item
className="m-0 w-40"
label={t('label.type')}
name="testCaseType">
<Select
data-testid="test-case-type-select-filter"
options={TEST_CASE_TYPE_OPTION}
/>
</Form.Item>
<Form.Item
className="m-0 w-40"
label={t('label.status')}
name="testCaseStatus">
<Select
data-testid="status-select-filter"
options={TEST_CASE_STATUS_OPTION}
/>
</Form.Item>
<Form.Item
className="m-0"
label={t('label.last-run')}
name="lastRunRange">
<DatePicker.RangePicker
allowClear
showNow
data-testid="last-run-range-picker"
/>
</Form.Item>
</Space>
</Form>
</Col> </Col>
<Col span={24}>{summaryPanel}</Col> <Col span={24}>{summaryPanel}</Col>
<Col span={24}> <Col span={24}>

View File

@ -13,8 +13,7 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { DataQualityPageTabs } from '../../../pages/DataQuality/DataQualityPage.interface'; import { DataQualityPageTabs } from '../../../pages/DataQuality/DataQualityPage.interface';
import { searchQuery } from '../../../rest/searchAPI'; import { getListTestCaseBySearch } from '../../../rest/testAPI';
import { getListTestCase } from '../../../rest/testAPI';
import { TestCases } from './TestCases.component'; import { TestCases } from './TestCases.component';
const testCasePermission = { const testCasePermission = {
@ -42,7 +41,7 @@ jest.mock('../../../context/PermissionProvider/PermissionProvider', () => ({
jest.mock('../../../rest/testAPI', () => { jest.mock('../../../rest/testAPI', () => {
return { return {
...jest.requireActual('../../../rest/testAPI'), ...jest.requireActual('../../../rest/testAPI'),
getListTestCase: jest getListTestCaseBySearch: jest
.fn() .fn()
.mockImplementation(() => .mockImplementation(() =>
Promise.resolve({ data: [], paging: { total: 0 } }) Promise.resolve({ data: [], paging: { total: 0 } })
@ -107,32 +106,57 @@ describe('TestCases component', () => {
expect( expect(
await screen.findByText('DataQualityTab.component') await screen.findByText('DataQualityTab.component')
).toBeInTheDocument(); ).toBeInTheDocument();
expect(
await screen.findByTestId('table-select-filter')
).toBeInTheDocument();
expect(
await screen.findByTestId('last-run-range-picker')
).toBeInTheDocument();
expect(
await screen.findByTestId('status-select-filter')
).toBeInTheDocument();
expect(
await screen.findByTestId('test-case-type-select-filter')
).toBeInTheDocument();
expect(
await screen.findByTestId('platform-select-filter')
).toBeInTheDocument();
}); });
it('on page load getListTestCase API should call', async () => { it('on page load getListTestCaseBySearch API should call', async () => {
const mockGetListTestCase = getListTestCase as jest.Mock; const mockGetListTestCase = getListTestCaseBySearch as jest.Mock;
render(<TestCases {...mockProps} />); render(<TestCases {...mockProps} />);
expect(mockGetListTestCase).toHaveBeenCalledWith({ expect(mockGetListTestCase).toHaveBeenCalledWith({
fields: 'testDefinition,testCaseResult,testSuite,incidentId', fields: 'testCaseResult,testSuite,incidentId',
limit: 15, includeAllTests: true,
orderByLastExecutionDate: true, limit: 10,
offset: 0,
q: undefined,
testCaseStatus: undefined,
testCaseType: 'all',
sortField: 'testCaseResult.timestamp',
sortType: 'desc',
}); });
}); });
it('should call searchQuery api, if there is search term in URL', async () => { it('should call getListTestCaseBySearch api, if there is search term in URL', async () => {
const mockSearchQuery = searchQuery as jest.Mock; const mockSearchQuery = getListTestCaseBySearch as jest.Mock;
mockLocation.search = '?searchValue=sale'; mockLocation.search = '?searchValue=sale';
render(<TestCases {...mockProps} />); render(<TestCases {...mockProps} />);
expect(mockSearchQuery).toHaveBeenCalledWith({ expect(mockSearchQuery).toHaveBeenCalledWith({
fetchSource: false, fields: 'testCaseResult,testSuite,incidentId',
pageNumber: 1, includeAllTests: true,
pageSize: 15, limit: 10,
query: 'sale', offset: 0,
searchIndex: 'test_case_search_index', q: '*sale*',
testCaseStatus: undefined,
testCaseType: 'all',
sortField: 'testCaseResult.timestamp',
sortType: 'desc',
}); });
}); });
}); });

View File

@ -24,6 +24,7 @@ import {
ProfileSampleType, ProfileSampleType,
} from '../generated/entity/data/table'; } from '../generated/entity/data/table';
import { TestCaseStatus } from '../generated/tests/testCase'; import { TestCaseStatus } from '../generated/tests/testCase';
import { TestPlatform } from '../generated/tests/testDefinition';
import { TestCaseType } from '../rest/testAPI'; import { TestCaseType } from '../rest/testAPI';
import { import {
getCurrentMillis, getCurrentMillis,
@ -415,6 +416,11 @@ export const TEST_CASE_STATUS_OPTION = [
})), })),
]; ];
export const TEST_CASE_PLATFORM_OPTION = values(TestPlatform).map((value) => ({
label: value,
value: value,
}));
export const INITIAL_COLUMN_METRICS_VALUE = { export const INITIAL_COLUMN_METRICS_VALUE = {
countMetrics: INITIAL_COUNT_METRIC_VALUE, countMetrics: INITIAL_COUNT_METRIC_VALUE,
proportionMetrics: INITIAL_PROPORTION_METRIC_VALUE, proportionMetrics: INITIAL_PROPORTION_METRIC_VALUE,

View File

@ -808,6 +808,7 @@
"pipeline-name": "Pipeline-Name", "pipeline-name": "Pipeline-Name",
"pipeline-plural": "Pipelines", "pipeline-plural": "Pipelines",
"pipeline-state": "Pipeline-Status", "pipeline-state": "Pipeline-Status",
"platform": "Platform",
"please-enter-value": "Bitte einen Wert für {{name}} eingeben", "please-enter-value": "Bitte einen Wert für {{name}} eingeben",
"please-password-type-first": "Bitte zuerst das Passwort eingeben", "please-password-type-first": "Bitte zuerst das Passwort eingeben",
"please-select": "Bitte auswählen", "please-select": "Bitte auswählen",

View File

@ -808,6 +808,7 @@
"pipeline-name": "Pipeline Name", "pipeline-name": "Pipeline Name",
"pipeline-plural": "Pipelines", "pipeline-plural": "Pipelines",
"pipeline-state": "Pipeline State", "pipeline-state": "Pipeline State",
"platform": "Platform",
"please-enter-value": "Please enter {{name}} value", "please-enter-value": "Please enter {{name}} value",
"please-password-type-first": "Please type password first", "please-password-type-first": "Please type password first",
"please-select": "Please Select", "please-select": "Please Select",

View File

@ -808,6 +808,7 @@
"pipeline-name": "Nombre de la pipeline", "pipeline-name": "Nombre de la pipeline",
"pipeline-plural": "Pipelines", "pipeline-plural": "Pipelines",
"pipeline-state": "Estado de la pipeline", "pipeline-state": "Estado de la pipeline",
"platform": "Platform",
"please-enter-value": "Ingrese el valor de {{name}}", "please-enter-value": "Ingrese el valor de {{name}}",
"please-password-type-first": "Ingrese primero la contraseña", "please-password-type-first": "Ingrese primero la contraseña",
"please-select": "Por favor seleccione", "please-select": "Por favor seleccione",

View File

@ -808,6 +808,7 @@
"pipeline-name": "Nom du Pipeline", "pipeline-name": "Nom du Pipeline",
"pipeline-plural": "Pipelines", "pipeline-plural": "Pipelines",
"pipeline-state": "État du Pipeline", "pipeline-state": "État du Pipeline",
"platform": "Platform",
"please-enter-value": "Merci d'entrer une valeur pour {{name}} ", "please-enter-value": "Merci d'entrer une valeur pour {{name}} ",
"please-password-type-first": "Merci d'entrer le mot de passe d'abord", "please-password-type-first": "Merci d'entrer le mot de passe d'abord",
"please-select": "Merci de sélectionner", "please-select": "Merci de sélectionner",

View File

@ -808,6 +808,7 @@
"pipeline-name": "שם תהליך הטעינה/עיבוד", "pipeline-name": "שם תהליך הטעינה/עיבוד",
"pipeline-plural": "תהליכי טעינה/עיבוד", "pipeline-plural": "תהליכי טעינה/עיבוד",
"pipeline-state": "מצב תהליך הטעינה/עיבוד", "pipeline-state": "מצב תהליך הטעינה/עיבוד",
"platform": "Platform",
"please-enter-value": "נא להזין את ערך {{name}}", "please-enter-value": "נא להזין את ערך {{name}}",
"please-password-type-first": "נא להקליד סיסמה תחילה", "please-password-type-first": "נא להקליד סיסמה תחילה",
"please-select": "בחר בבקשה", "please-select": "בחר בבקשה",

View File

@ -808,6 +808,7 @@
"pipeline-name": "パイプライン名", "pipeline-name": "パイプライン名",
"pipeline-plural": "パイプライン", "pipeline-plural": "パイプライン",
"pipeline-state": "パイプラインの状態", "pipeline-state": "パイプラインの状態",
"platform": "Platform",
"please-enter-value": "{{name}}の値を入力してください", "please-enter-value": "{{name}}の値を入力してください",
"please-password-type-first": "パスワードを入力してください", "please-password-type-first": "パスワードを入力してください",
"please-select": "選択してください", "please-select": "選択してください",

View File

@ -808,6 +808,7 @@
"pipeline-name": "Pipelinenaam", "pipeline-name": "Pipelinenaam",
"pipeline-plural": "Pipelines", "pipeline-plural": "Pipelines",
"pipeline-state": "Pipelinestatus", "pipeline-state": "Pipelinestatus",
"platform": "Platform",
"please-enter-value": "Voer alstublieft de waarde voor {{name}} in", "please-enter-value": "Voer alstublieft de waarde voor {{name}} in",
"please-password-type-first": "Typ eerst het wachtwoord alstublieft", "please-password-type-first": "Typ eerst het wachtwoord alstublieft",
"please-select": "Selecteer alstublieft", "please-select": "Selecteer alstublieft",

View File

@ -808,6 +808,7 @@
"pipeline-name": "Nome do Pipeline", "pipeline-name": "Nome do Pipeline",
"pipeline-plural": "Pipelines", "pipeline-plural": "Pipelines",
"pipeline-state": "Estado do Pipeline", "pipeline-state": "Estado do Pipeline",
"platform": "Platform",
"please-enter-value": "Por favor, insira o valor de {{name}}", "please-enter-value": "Por favor, insira o valor de {{name}}",
"please-password-type-first": "Por favor, digite a senha primeiro", "please-password-type-first": "Por favor, digite a senha primeiro",
"please-select": "Por favor, Selecione", "please-select": "Por favor, Selecione",

View File

@ -808,6 +808,7 @@
"pipeline-name": "Наименование пайплайна", "pipeline-name": "Наименование пайплайна",
"pipeline-plural": "Пайплайны", "pipeline-plural": "Пайплайны",
"pipeline-state": "Состояние", "pipeline-state": "Состояние",
"platform": "Platform",
"please-enter-value": "Пожалуйста введите значение {{name}} ", "please-enter-value": "Пожалуйста введите значение {{name}} ",
"please-password-type-first": "Пожалуйста, сначала введите пароль", "please-password-type-first": "Пожалуйста, сначала введите пароль",
"please-select": "Пожалуйста выберите", "please-select": "Пожалуйста выберите",

View File

@ -808,6 +808,7 @@
"pipeline-name": "工作流名称", "pipeline-name": "工作流名称",
"pipeline-plural": "工作流", "pipeline-plural": "工作流",
"pipeline-state": "工作流状态", "pipeline-state": "工作流状态",
"platform": "Platform",
"please-enter-value": "请输入{{name}}值", "please-enter-value": "请输入{{name}}值",
"please-password-type-first": "请先输入密码", "please-password-type-first": "请先输入密码",
"please-select": "请选择", "please-select": "请选择",

View File

@ -14,6 +14,7 @@
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { PagingResponse, RestoreRequestType } from 'Models'; import { PagingResponse, RestoreRequestType } from 'Models';
import { SORT_ORDER } from '../enums/common.enum';
import { CreateTestCase } from '../generated/api/tests/createTestCase'; import { CreateTestCase } from '../generated/api/tests/createTestCase';
import { CreateTestSuite } from '../generated/api/tests/createTestSuite'; import { CreateTestSuite } from '../generated/api/tests/createTestSuite';
import { import {
@ -55,6 +56,18 @@ export type ListTestCaseParams = ListParams & {
testCaseStatus?: TestCaseStatus; testCaseStatus?: TestCaseStatus;
testCaseType?: TestCaseType; testCaseType?: TestCaseType;
}; };
export type ListTestCaseParamsBySearch = Omit<
ListTestCaseParams,
'orderByLastExecutionDate'
> & {
q?: string;
sortType?: SORT_ORDER;
sortField?: string;
startTimestamp?: number;
endTimestamp?: number;
testPlatforms?: TestPlatform[];
offset?: number;
};
export type ListTestDefinitionsParams = ListParams & { export type ListTestDefinitionsParams = ListParams & {
entityType?: EntityType; entityType?: EntityType;
@ -91,6 +104,19 @@ export const getListTestCase = async (params?: ListTestCaseParams) => {
return response.data; return response.data;
}; };
export const getListTestCaseBySearch = async (
params?: ListTestCaseParamsBySearch
) => {
const response = await APIClient.get<PagingResponse<TestCase[]>>(
`${testCaseUrl}/search/list`,
{
params,
}
);
return response.data;
};
export const getListTestCaseResults = async ( export const getListTestCaseResults = async (
fqn: string, fqn: string,
params?: ListTestCaseResultsParams params?: ListTestCaseResultsParams