ui: worked on DQ feedback part 3 (#12149)

* ui: worked on DQ feedback part 3

* updated name of test suite ingestion name
- Added description field in test case expand view
- Added functionality to update displayname of test case

* added testCase in re-indexing

* integrated permission in DQ
This commit is contained in:
Shailesh Parmar 2023-06-26 22:35:23 +05:30 committed by GitHub
parent 0010a4fe2a
commit fecb65c513
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 284 additions and 256 deletions

View File

@ -116,7 +116,7 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
...testCase,
parameterValues,
description,
name: value.name,
displayName: value.displayName,
};
const jsonPatch = compare(testCase, updatedTestCase);
@ -124,10 +124,10 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
try {
setIsLoadingOnSave(true);
const updateRes = await updateTestCaseById(
testCase.id || '',
testCase.id ?? '',
jsonPatch
);
onUpdate && onUpdate(updateRes);
onUpdate?.(updateRes);
showSuccessToast(
t('server.update-entity-success', { entity: t('label.test-case') })
);
@ -216,17 +216,17 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
layout="vertical"
name="tableTestForm"
onFinish={handleFormSubmit}>
<Form.Item required label={`${t('label.table')}:`} name="table">
<Form.Item required label={`${t('label.table')}`} name="table">
<Input disabled />
</Form.Item>
{isColumn && (
<Form.Item required label={`${t('label.column')}:`} name="column">
<Form.Item required label={`${t('label.column')}`} name="column">
<Input disabled />
</Form.Item>
)}
<Form.Item
required
label={`${t('label.name')}:`}
label={`${t('label.name')}`}
name="name"
rules={[
{
@ -236,6 +236,9 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
]}>
<Input disabled placeholder={t('message.enter-test-case-name')} />
</Form.Item>
<Form.Item label={t('label.display-name')} name="displayName">
<Input placeholder={t('message.enter-test-case-name')} />
</Form.Item>
<Form.Item
required
label={`${t('label.test-entity', {
@ -247,7 +250,7 @@ const EditTestCaseModal: React.FC<EditTestCaseModalProps> = ({
{GenerateParamsField()}
<Form.Item label={`${t('label.description')}:`} name="description">
<Form.Item label={`${t('label.description')}`} name="description">
<RichTextEditor
height="200px"
initialValue={testCase?.description || ''}

View File

@ -22,6 +22,7 @@ import {
deployIngestionPipelineById,
updateIngestionPipeline as putIngestionPipeline,
} from 'rest/ingestionPipelineAPI';
import { getIngestionName } from 'utils/ServiceUtils';
import {
DEPLOYED_PROGRESS_VAL,
INGESTION_PROGRESS_END_VAL,
@ -36,7 +37,8 @@ import {
import { IngestionPipeline } from '../../generated/entity/services/ingestionPipelines/ingestionPipeline';
import {
getIngestionFrequency,
replaceSpaceWith_,
getNameFromFQN,
replaceAllSpacialCharWith_,
Transi18next,
} from '../../utils/CommonUtils';
import { showErrorToast } from '../../utils/ToastUtils';
@ -113,7 +115,12 @@ const TestSuiteIngestion: React.FC<TestSuiteIngestionProps> = ({
};
const createIngestionPipeline = async (repeatFrequency: string) => {
const updatedName = replaceSpaceWith_(testSuite.name);
const tableName = replaceAllSpacialCharWith_(
getNameFromFQN(
testSuite.executableEntityReference?.fullyQualifiedName ?? ''
)
);
const updatedName = getIngestionName(tableName, PipelineType.TestSuite);
const ingestionPayload: CreateIngestionPipeline = {
airflowConfig: {
@ -121,10 +128,10 @@ const TestSuiteIngestion: React.FC<TestSuiteIngestionProps> = ({
? undefined
: repeatFrequency,
},
name: `${updatedName}_${PipelineType.TestSuite}`,
name: updatedName,
pipelineType: PipelineType.TestSuite,
service: {
id: testSuite.id || '',
id: testSuite.id ?? '',
type: camelCase(PipelineType.TestSuite),
},
sourceConfig: {

View File

@ -138,16 +138,18 @@ const TestCaseForm: React.FC<TestCaseFormProps> = ({
: value,
})
);
const name =
value.testName?.trim() ||
`${columnName ? columnName : table.name}_${snakeCase(
selectedTestType
)}_${cryptoRandomString({
length: 4,
type: 'alphanumeric',
})}`;
return {
name:
value.testName?.trim() ||
`${columnName ? columnName : table.name}_${snakeCase(
selectedTestType
)}_${cryptoRandomString({
length: 4,
type: 'alphanumeric',
})}`,
name,
displayName: name,
entityLink: generateEntityLink(
isColumnFqn ? `${decodedEntityFQN}.${columnName}` : decodedEntityFQN,
isColumnFqn

View File

@ -10,11 +10,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Checkbox, Col, List, Modal, Row, Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import { Button, Checkbox, Col, List, Row, Space, Typography } from 'antd';
import Searchbar from 'components/common/searchbar/Searchbar';
import Loader from 'components/Loader/Loader';
import { getTableTabPath, PAGE_SIZE_MEDIUM } from 'constants/constants';
import { getTableTabPath, PAGE_SIZE } from 'constants/constants';
import { SearchIndex } from 'enums/search.enum';
import { TestCase } from 'generated/tests/testCase';
import {
@ -26,13 +25,11 @@ import React, { UIEventHandler, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { searchQuery } from 'rest/searchAPI';
import { addTestCaseToLogicalTestSuite } from 'rest/testAPI';
import { getNameFromFQN } from 'utils/CommonUtils';
import { getEntityName } from 'utils/EntityUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { getEntityFqnFromEntityLink } from 'utils/TableUtils';
import { showErrorToast } from 'utils/ToastUtils';
import { AddTestCaseModalProps } from './AddTestCaseModal.interface';
import { AddTestCaseModalProps } from './AddTestCaseList.interface';
// Todo: need to help from backend guys for ES query
// export const getQueryFilterToExcludeTest = (testCase: EntityReference[]) => ({
@ -47,12 +44,12 @@ import { AddTestCaseModalProps } from './AddTestCaseModal.interface';
// },
// });
export const AddTestCaseModal = ({
open,
export const AddTestCaseList = ({
onCancel,
existingTest,
testSuiteId,
onSubmit,
cancelText,
submitText,
}: AddTestCaseModalProps) => {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState<string>('');
@ -74,7 +71,7 @@ export const AddTestCaseModal = ({
setIsLoading(true);
const res = await searchQuery({
pageNumber: page,
pageSize: PAGE_SIZE_MEDIUM,
pageSize: PAGE_SIZE,
searchIndex: SearchIndex.TEST_CASE,
query: searchText,
// queryFilter: getQueryFilterToExcludeTest(existingTest),
@ -100,17 +97,8 @@ export const AddTestCaseModal = ({
const testCaseIds = [...(selectedItems?.values() ?? [])].map(
(test) => test.id ?? ''
);
try {
await addTestCaseToLogicalTestSuite({ testCaseIds, testSuiteId });
onSubmit();
onCancel();
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
setIsLoading(false);
}
onSubmit(testCaseIds);
setIsLoading(false);
};
const onScroll: UIEventHandler<HTMLElement> = useCallback(
@ -126,7 +114,7 @@ export const AddTestCaseModal = ({
});
}
},
[searchTerm, totalCount, items]
[searchTerm, totalCount, items, isLoading]
);
const handleCardClick = (details: TestCase) => {
@ -160,99 +148,92 @@ export const AddTestCaseModal = ({
}
};
useEffect(() => {
if (open) {
fetchTestCases({ searchText: searchTerm });
}
}, [open, searchTerm]);
fetchTestCases({ searchText: searchTerm });
}, [searchTerm]);
return (
<Modal
centered
destroyOnClose
closable={false}
okButtonProps={{
loading: isLoading,
}}
open={open}
title={t('label.add-entity', { entity: t('label.test-case-plural') })}
width={750}
onCancel={onCancel}
onOk={handleSubmit}>
<Row gutter={[0, 16]}>
<Col span={24}>
<Searchbar
removeMargin
showClearSearch
showLoadingStatus
placeholder={t('label.search-entity', {
entity: t('label.test-case-plural'),
})}
searchValue={searchTerm}
onSearch={handleSearch}
/>
</Col>
<Col span={24}>
<List loading={{ spinning: false, indicator: <Loader /> }}>
<VirtualList
data={items}
height={500}
itemKey="id"
onScroll={onScroll}>
{({ _source: test }) => {
const tableFqn = getEntityFqnFromEntityLink(test.entityLink);
const tableName = getNameFromFQN(tableFqn);
const isColumn = test.entityLink.includes('::columns::');
<Row gutter={[0, 16]}>
<Col span={24}>
<Searchbar
removeMargin
showClearSearch
showLoadingStatus
placeholder={t('label.search-entity', {
entity: t('label.test-case-plural'),
})}
searchValue={searchTerm}
onSearch={handleSearch}
/>
</Col>
<Col span={24}>
<List loading={{ spinning: false, indicator: <Loader /> }}>
<VirtualList
data={items}
height={500}
itemKey="id"
onScroll={onScroll}>
{({ _source: test }) => {
const tableFqn = getEntityFqnFromEntityLink(test.entityLink);
const tableName = getNameFromFQN(tableFqn);
const isColumn = test.entityLink.includes('::columns::');
return (
<Space
className="m-b-md border rounded-4 p-sm cursor-pointer"
direction="vertical"
onClick={() => handleCardClick(test)}>
<Space className="justify-between w-full">
<Typography.Paragraph
className="m-0 font-medium text-base"
data-testid={test.name}>
{getEntityName(test)}
</Typography.Paragraph>
return (
<Space
className="m-b-md border rounded-4 p-sm cursor-pointer"
direction="vertical"
onClick={() => handleCardClick(test)}>
<Space className="justify-between w-full">
<Typography.Paragraph
className="m-0 font-medium text-base"
data-testid={test.name}>
{getEntityName(test)}
</Typography.Paragraph>
<Checkbox checked={selectedItems?.has(test.id ?? '')} />
</Space>
<Typography.Paragraph className="m-0">
{getEntityName(test.testDefinition)}
</Typography.Paragraph>
<Typography.Paragraph className="m-0">
<Link
data-testid="table-link"
to={getTableTabPath(tableFqn, 'profiler')}
onClick={(e) => e.stopPropagation()}>
{tableName}
</Link>
</Typography.Paragraph>
{isColumn && (
<Space>
<Typography.Text className="font-medium text-xs">{`${t(
'label.column'
)}:`}</Typography.Text>
<Typography.Text className="text-grey-muted text-xs">
{getNameFromFQN(
getDecodedFqn(
getEntityFqnFromEntityLink(
test.entityLink,
isColumn
),
true
)
) ?? '--'}
</Typography.Text>
</Space>
)}
<Checkbox checked={selectedItems?.has(test.id ?? '')} />
</Space>
);
}}
</VirtualList>
</List>
</Col>
</Row>
</Modal>
<Typography.Paragraph className="m-0">
{getEntityName(test.testDefinition)}
</Typography.Paragraph>
<Typography.Paragraph className="m-0">
<Link
data-testid="table-link"
to={getTableTabPath(tableFqn, 'profiler')}
onClick={(e) => e.stopPropagation()}>
{tableName}
</Link>
</Typography.Paragraph>
{isColumn && (
<Space>
<Typography.Text className="font-medium text-xs">{`${t(
'label.column'
)}:`}</Typography.Text>
<Typography.Text className="text-grey-muted text-xs">
{getNameFromFQN(
getDecodedFqn(
getEntityFqnFromEntityLink(
test.entityLink,
isColumn
),
true
)
) ?? '--'}
</Typography.Text>
</Space>
)}
</Space>
);
}}
</VirtualList>
</List>
</Col>
<Col className="d-flex justify-end items-center p-y-xss" span={24}>
<Button type="link" onClick={onCancel}>
{cancelText ?? t('label.cancel')}
</Button>
<Button loading={isLoading} type="primary" onClick={handleSubmit}>
{submitText ?? t('label.submit')}
</Button>
</Col>
</Row>
);
};

View File

@ -13,9 +13,9 @@
import { EntityReference } from 'generated/tests/testCase';
export interface AddTestCaseModalProps {
open: boolean;
onCancel: () => void;
onSubmit: () => void;
existingTest: EntityReference[];
testSuiteId: string;
onCancel?: () => void;
onSubmit: (testCaseIds: string[]) => void;
existingTest?: EntityReference[];
cancelText?: string;
submitText?: string;
}

View File

@ -13,6 +13,7 @@
import { Col, Row } from 'antd';
import { AxiosError } from 'axios';
import { SummaryCard } from 'components/common/SummaryCard/SummaryCard.component';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import { TestSummary } from 'generated/tests/testSuite';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -23,6 +24,8 @@ import { SummaryPanelProps } from './SummaryPanel.interface';
export const SummaryPanel = ({ testSuiteId }: SummaryPanelProps) => {
const { t } = useTranslation();
const { permissions } = usePermissionProvider();
const { testCase: testCasePermission } = permissions;
const [summary, setSummary] = useState<TestSummary>();
const [isLoading, setIsLoading] = useState(true);
@ -40,8 +43,10 @@ export const SummaryPanel = ({ testSuiteId }: SummaryPanelProps) => {
};
useEffect(() => {
fetchTestSummary();
}, [testSuiteId]);
if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) {
fetchTestSummary();
}
}, [testSuiteId, testCasePermission]);
return (
<Row wrap gutter={[16, 16]}>

View File

@ -13,6 +13,7 @@
import { Col, Row } from 'antd';
import { AxiosError } from 'axios';
import Searchbar from 'components/common/searchbar/Searchbar';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import DataQualityTab from 'components/ProfilerDashboard/component/DataQualityTab';
import { INITIAL_PAGING_VALUE, PAGE_SIZE } from 'constants/constants';
import { SearchIndex } from 'enums/search.enum';
@ -29,7 +30,11 @@ import QueryString from 'qs';
import React, { useEffect, useMemo, useState } from 'react';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import { searchQuery } from 'rest/searchAPI';
import { getListTestCase, ListTestCaseParams } from 'rest/testAPI';
import {
getListTestCase,
getTestCaseById,
ListTestCaseParams,
} from 'rest/testAPI';
import { showErrorToast } from 'utils/ToastUtils';
import { DataQualitySearchParams } from '../DataQuality.interface';
import { SummaryPanel } from '../SummaryPannel/SummaryPanel.component';
@ -38,6 +43,8 @@ export const TestCases = () => {
const history = useHistory();
const location = useLocation();
const { tab } = useParams<{ tab: DataQualityPageTabs }>();
const { permissions } = usePermissionProvider();
const { testCase: testCasePermission } = permissions;
const params = useMemo(() => {
const search = location.search;
@ -115,16 +122,31 @@ export const TestCases = () => {
pageSize: PAGE_SIZE,
searchIndex: SearchIndex.TEST_CASE,
query: searchValue,
fetchSource: false,
});
const hits = (
const promise = (
response.hits.hits as SearchHitBody<
SearchIndex.TEST_CASE,
TestCaseSearchSource
>[]
).map((value) => value._source);
).map((value) =>
getTestCaseById(value._id ?? '', {
fields: 'testDefinition,testCaseResult,testSuite',
})
);
const value = await Promise.allSettled(promise);
const testSuites = value.reduce((prev, curr) => {
if (curr.status === 'fulfilled') {
return [...prev, curr.value.data];
}
return prev;
}, [] as TestCase[]);
setTestCase({
data: hits,
data: testSuites,
paging: { total: response.hits.total.value ?? 0 },
});
} catch (error) {
@ -151,14 +173,16 @@ export const TestCases = () => {
};
useEffect(() => {
if (tab === DataQualityPageTabs.TEST_CASES) {
if (searchValue) {
searchTestCases();
} else {
fetchTestCases();
if (testCasePermission?.ViewAll || testCasePermission?.ViewBasic) {
if (tab === DataQualityPageTabs.TEST_CASES) {
if (searchValue) {
searchTestCases();
} else {
fetchTestCases();
}
}
}
}, [tab, searchValue]);
}, [tab, searchValue, testCasePermission]);
return (
<Row className="p-x-lg p-t-md" gutter={[16, 16]}>

View File

@ -17,6 +17,7 @@ import FilterTablePlaceHolder from 'components/common/error-with-placeholder/Fil
import NextPrevious from 'components/common/next-previous/NextPrevious';
import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component';
import Searchbar from 'components/common/searchbar/Searchbar';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import { TableProfilerTab } from 'components/ProfilerDashboard/profilerDashboard.interface';
import ProfilerProgressWidget from 'components/TableProfiler/Component/ProfilerProgressWidget';
import {
@ -55,6 +56,8 @@ export const TestSuites = () => {
useParams<{ tab: DataQualityPageTabs }>();
const history = useHistory();
const location = useLocation();
const { permissions } = usePermissionProvider();
const { testSuite: testSuitePermission } = permissions;
const [testSuites, setTestSuites] = useState<PagingResponse<TestSuite[]>>({
data: [],
@ -177,8 +180,10 @@ export const TestSuites = () => {
};
useEffect(() => {
fetchTestSuites();
}, [tab]);
if (testSuitePermission?.ViewAll || testSuitePermission?.ViewBasic) {
fetchTestSuites();
}
}, [tab, testSuitePermission]);
return (
<Row className="p-x-lg p-t-md" gutter={[16, 16]}>
@ -192,13 +197,14 @@ export const TestSuites = () => {
/>
</Col>
<Col>
{tab === DataQualityPageTabs.TEST_SUITES && (
<Link to={ROUTES.ADD_TEST_SUITES}>
<Button type="primary">
{t('label.add-entity', { entity: t('label.test-suite') })}
</Button>
</Link>
)}
{tab === DataQualityPageTabs.TEST_SUITES &&
testSuitePermission?.Create && (
<Link to={ROUTES.ADD_TEST_SUITES}>
<Button type="primary">
{t('label.add-entity', { entity: t('label.test-suite') })}
</Button>
</Link>
)}
</Col>
</Row>
</Col>

View File

@ -11,8 +11,9 @@
* limitations under the License.
*/
import { Button, Col, Row, Typography } from 'antd';
import { Button, Col, Row, Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
import {
GREEN_3,
@ -97,13 +98,13 @@ const TestSummary: React.FC<TestSummaryProps> = ({
const values = result.testResultValue?.reduce((acc, curr) => {
return {
...acc,
[curr.name || 'value']: round(parseFloat(curr.value ?? ''), 2) || 0,
[curr.name ?? 'value']: round(parseFloat(curr.value ?? ''), 2) || 0,
};
}, {});
chartData.push({
name: getFormattedDateFromSeconds(result.timestamp as number),
status: result.testCaseStatus || '',
status: result.testCaseStatus ?? '',
...values,
});
});
@ -111,9 +112,9 @@ const TestSummary: React.FC<TestSummaryProps> = ({
setChartData({
information:
currentData[0]?.testResultValue?.map((info, i) => ({
label: info.name || '',
label: info.name ?? '',
color: COLORS[i],
})) || [],
})) ?? [],
data: chartData,
});
};
@ -251,13 +252,16 @@ const TestSummary: React.FC<TestSummaryProps> = ({
if (isSqlQuery) {
return (
<Row className="sql-expression-container" gutter={8} key={param.name}>
<Col span={showExpandIcon ? 2 : 3}>
<Row
className="sql-expression-container"
gutter={[8, 8]}
key={param.name}>
<Col span={24}>
<Typography.Text className="text-grey-muted">
{`${param.name}:`}
</Typography.Text>
</Col>
<Col span={showExpandIcon ? 22 : 21}>
<Col span={24}>
<SchemaEditor
editorClass="table-query-editor"
mode={{ name: CSMode.SQL }}
@ -344,30 +348,37 @@ const TestSummary: React.FC<TestSummaryProps> = ({
)}
</Col>
<Col span={24}>
{showParameters && (
<Row align="top" data-testid="params-container" gutter={16}>
<Row align="top" data-testid="params-container" gutter={[16, 16]}>
{showParameters && (
<Col>
<Typography.Text className="text-grey-muted">
{`${t('label.parameter')}:`}
</Typography.Text>
</Col>
<Col>
{!isEmpty(parameterValuesWithoutSqlExpression) ? (
<Row className="parameter-value-container" gutter={[4, 4]}>
{parameterValuesWithoutSqlExpression?.map(showParamsData)}
</Row>
) : (
<Typography.Text type="secondary">
<Typography.Text className="m-l-xs" type="secondary">
{t('label.no-parameter-available')}
</Typography.Text>
)}
</Col>
</Row>
)}
{!isUndefined(parameterValuesWithSqlExpression)
? parameterValuesWithSqlExpression.map(showParamsData)
: null}
)}
{!isUndefined(parameterValuesWithSqlExpression) ? (
<Col>{parameterValuesWithSqlExpression.map(showParamsData)}</Col>
) : null}
{data.description && (
<Col>
<Space direction="vertical" size={4}>
<Typography.Text className="text-grey-muted">
{`${t('label.description')}:`}
</Typography.Text>
<RichTextEditorPreviewer markdown={data.description} />
</Space>
</Col>
)}
</Row>
</Col>
</Row>
);

View File

@ -118,6 +118,7 @@ const AddTestSuiteForm: React.FC<AddTestSuiteFormProps> = ({
<Form.Item label={t('label.description')} name="description">
<RichTextEditor
data-testid="test-suite-description"
height="200px"
initialValue={testSuite?.description ?? ''}
onTextChange={(value) => form.setFieldsValue({ description: value })}
/>

View File

@ -11,34 +11,28 @@
* limitations under the License.
*/
import { Button, Col, Form, Row, Select, Space, Typography } from 'antd';
import { Col, Row, Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import RightPanel from 'components/AddDataQualityTest/components/RightPanel';
import { getRightPanelForAddTestSuitePage } from 'components/AddDataQualityTest/rightPanelData';
import { AddTestCaseList } from 'components/AddTestCaseList/AddTestCaseList.component';
import ResizablePanels from 'components/common/ResizablePanels/ResizablePanels';
import SuccessScreen from 'components/common/success-screen/SuccessScreen';
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
import IngestionStepper from 'components/IngestionStepper/IngestionStepper.component';
import { HTTP_STATUS_CODE } from 'constants/auth.constants';
import { PAGE_SIZE_LARGE } from 'constants/constants';
import {
STEPS_FOR_ADD_TEST_SUITE,
TEST_SUITE_STEPPER_BREADCRUMB,
} from 'constants/TestSuite.constant';
import { FormSubmitType } from 'enums/form.enum';
import { OwnerType } from 'enums/user.enum';
import { TestCase } from 'generated/tests/testCase';
import { TestSuite } from 'generated/tests/testSuite';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import {
addTestCaseToLogicalTestSuite,
createTestSuites,
getListTestCase,
} from 'rest/testAPI';
import { addTestCaseToLogicalTestSuite, createTestSuites } from 'rest/testAPI';
import { getCurrentUserId } from 'utils/CommonUtils';
import { getEntityName } from 'utils/EntityUtils';
import { getTestSuitePath } from 'utils/RouterUtils';
import { showErrorToast } from 'utils/ToastUtils';
import AddTestSuiteForm from '../AddTestSuiteForm/AddTestSuiteForm';
@ -48,11 +42,9 @@ const TestSuiteStepper = () => {
const history = useHistory();
const [activeServiceStep, setActiveServiceStep] = useState(1);
const [testSuiteResponse, setTestSuiteResponse] = useState<TestSuite>();
const [testCases, setTestCases] = useState<TestCase[]>([]);
const [selectedTestCase, setSelectedTestCase] = useState<string[]>([]);
const handleViewTestSuiteClick = () => {
history.push(getTestSuitePath(testSuiteResponse?.fullyQualifiedName || ''));
history.push(getTestSuitePath(testSuiteResponse?.fullyQualifiedName ?? ''));
};
const handleTestSuitNextClick = (data: TestSuite) => {
@ -60,7 +52,7 @@ const TestSuiteStepper = () => {
setActiveServiceStep(2);
};
const onSubmit = async (data: { testCase: string[] }) => {
const onSubmit = async (data: string[]) => {
try {
const owner = {
id: getCurrentUserId(),
@ -74,7 +66,7 @@ const TestSuiteStepper = () => {
});
setTestSuiteResponse(response);
await addTestCaseToLogicalTestSuite({
testCaseIds: data.testCase,
testCaseIds: data,
testSuiteId: response.id ?? '',
});
setActiveServiceStep(3);
@ -100,61 +92,15 @@ const TestSuiteStepper = () => {
}
};
const fetchTestCases = async () => {
try {
const response = await getListTestCase({ limit: PAGE_SIZE_LARGE });
setTestCases(response.data);
} catch (error) {
setTestCases([]);
}
};
useEffect(() => {
fetchTestCases();
}, []);
const selectTestCase = useMemo(() => {
return (
<Form
data-testid="test-case-form"
initialValues={{ testCase: selectedTestCase }}
layout="vertical"
name="selectTestCase"
onFinish={onSubmit}
onValuesChange={({ testCase }) => setSelectedTestCase(testCase)}>
<Form.Item label={t('label.test-case-plural')} name="testCase">
<Select
mode="multiple"
placeholder={t('label.please-select-entity', {
entity: t('label.test-case-plural'),
})}>
{testCases.map((test) => (
<Select.Option key={test.id}>{getEntityName(test)}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item noStyle>
<Space className="w-full justify-end" size={16}>
<Button
data-testid="back-button"
onClick={() => setActiveServiceStep(1)}>
{t('label.back')}
</Button>
<Button
data-testid="submit-button"
htmlType="submit"
type="primary">
{t('label.submit')}
</Button>
</Space>
</Form.Item>
</Form>
);
}, [testCases, selectedTestCase, testSuiteResponse]);
const RenderSelectedTab = useCallback(() => {
if (activeServiceStep === 2) {
return selectTestCase;
return (
<AddTestCaseList
cancelText={t('label.back')}
onCancel={() => setActiveServiceStep(1)}
onSubmit={onSubmit}
/>
);
} else if (activeServiceStep === 3) {
return (
<SuccessScreen

View File

@ -72,6 +72,10 @@ export const ELASTIC_SEARCH_INDEX_ENTITIES = [
value: 'query',
label: t('label.query'),
},
{
value: 'testCase',
label: t('label.test-case'),
},
];
export const ELASTIC_SEARCH_INITIAL_VALUES = {

View File

@ -11,9 +11,9 @@
* limitations under the License.
*/
import { Button, Col, Row, Space } from 'antd';
import { Button, Col, Modal, Row, Space } from 'antd';
import { AxiosError } from 'axios';
import { AddTestCaseModal } from 'components/AddTestCaseModal/AddTestCaseModal.component';
import { AddTestCaseList } from 'components/AddTestCaseList/AddTestCaseList.component';
import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider';
import Description from 'components/common/description/Description';
import ManageButton from 'components/common/entityPageInfo/ManageButton/ManageButton';
@ -38,6 +38,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import {
addTestCaseToLogicalTestSuite,
getListTestCase,
getTestSuiteByName,
ListTestCaseParams,
@ -154,6 +155,19 @@ const TestSuiteDetailsPage = () => {
fetchTestCases();
};
const handleAddTestCaseSubmit = async (testCaseIds: string[]) => {
try {
await addTestCaseToLogicalTestSuite({
testCaseIds,
testSuiteId: testSuite?.id ?? '',
});
setIsTestCaseModalOpen(false);
fetchTestCases();
} catch (error) {
showErrorToast(error as AxiosError);
}
};
const fetchTestSuiteByName = async () => {
try {
const response = await getTestSuiteByName(testSuiteFQN, {
@ -309,11 +323,16 @@ const TestSuiteDetailsPage = () => {
titleLinks={slashedBreadCrumb}
/>
<Space>
<Button
type="primary"
onClick={() => setIsTestCaseModalOpen(true)}>
{t('label.add-entity', { entity: t('label.test-case-plural') })}
</Button>
{(testSuitePermissions.EditAll ||
testSuitePermissions.EditTests) && (
<Button
type="primary"
onClick={() => setIsTestCaseModalOpen(true)}>
{t('label.add-entity', {
entity: t('label.test-case-plural'),
})}
</Button>
)}
<ManageButton
isRecursiveDelete
afterDeleteAction={afterDeleteAction}
@ -366,13 +385,22 @@ const TestSuiteDetailsPage = () => {
/>
</Col>
<Col span={24}>
<AddTestCaseModal
existingTest={testSuite?.tests ?? []}
<Modal
centered
destroyOnClose
closable={false}
footer={null}
open={isTestCaseModalOpen}
testSuiteId={testSuite?.id ?? ''}
onCancel={() => setIsTestCaseModalOpen(false)}
onSubmit={afterSubmitAction}
/>
title={t('label.add-entity', {
entity: t('label.test-case-plural'),
})}
width={750}>
<AddTestCaseList
existingTest={testSuite?.tests ?? []}
onCancel={() => setIsTestCaseModalOpen(false)}
onSubmit={handleAddTestCaseSubmit}
/>
</Modal>
</Col>
</Row>
</PageLayoutV1>

View File

@ -113,6 +113,16 @@ export const getTestCaseByFqn = async (
return response;
};
export const getTestCaseById = async (
id: string,
params?: Pick<ListParams, 'fields' | 'include'>
) => {
const response = await APIClient.get<TestCase>(`${testCaseUrl}/${id}`, {
params,
});
return response;
};
export const createTestCase = async (data: CreateTestCase) => {
const response = await APIClient.post<