mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-06 21:43:37 +00:00
Add Bundle Suite Form and Integrate with Data Quality Page
- Introduced a new BundleSuiteForm component for creating and managing test suites, including form fields for name, description, and test case selection. - Implemented styling for the Bundle Suite Form to enhance user experience and visual consistency. - Integrated the BundleSuiteForm into the DataQualityPage, allowing users to open the form as a modal for adding new test suites. - Added state management for modal visibility and handlers for opening and closing the Bundle Suite modal. - Enhanced the overall layout and functionality of the Data Quality page to accommodate the new test suite feature.
This commit is contained in:
parent
e5e2387208
commit
cb0fd485cf
@ -41,6 +41,7 @@ import {
|
||||
} from '../../../../constants/constants';
|
||||
import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants';
|
||||
import { DEFAULT_SCHEDULE_CRON_DAILY } from '../../../../constants/Schedular.constants';
|
||||
import { useAirflowStatus } from '../../../../context/AirflowStatusProvider/AirflowStatusProvider';
|
||||
import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { TagSource } from '../../../../generated/api/domains/createDataProduct';
|
||||
@ -161,6 +162,7 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
|
||||
const { t } = useTranslation();
|
||||
const { config } = useLimitStore();
|
||||
const [form] = useForm<FormValues>();
|
||||
const { isAirflowAvailable } = useAirflowStatus();
|
||||
|
||||
// =============================================
|
||||
// HOOKS - State (grouped by functionality)
|
||||
@ -759,7 +761,9 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
|
||||
};
|
||||
|
||||
const ingestion = await addIngestionPipeline(ingestionPayload);
|
||||
await deployIngestionPipelineById(ingestion.id ?? '');
|
||||
if (isAirflowAvailable) {
|
||||
await deployIngestionPipelineById(ingestion.id ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
showSuccessToast(
|
||||
@ -959,7 +963,7 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
|
||||
initialValues={{
|
||||
testLevel: TestLevel.TABLE,
|
||||
...testCaseClassBase.initialFormValues(),
|
||||
testName: replaceAllSpacialCharWith_(initialValues?.testName ?? ''),
|
||||
testName: initialValues?.testName,
|
||||
testTypeId: initialValues?.testTypeId,
|
||||
params: getInitialParamsValue,
|
||||
tags: initialValues?.tags || [],
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { DrawerProps } from 'antd';
|
||||
import { TestCase } from '../../../generated/tests/testCase';
|
||||
import { TestSuite } from '../../../generated/tests/testSuite';
|
||||
|
||||
export interface BundleSuiteFormProps {
|
||||
isDrawer?: boolean;
|
||||
drawerProps?: DrawerProps;
|
||||
className?: string;
|
||||
onCancel?: () => void;
|
||||
onSuccess?: (testSuite: TestSuite) => void;
|
||||
initialValues?: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
testCases?: TestCase[];
|
||||
};
|
||||
}
|
||||
|
||||
export type BundleSuiteFormData = {
|
||||
name: string;
|
||||
description?: string;
|
||||
testCases: TestCase[] | string[];
|
||||
cron?: string;
|
||||
enableDebugLog?: boolean;
|
||||
raiseOnError?: boolean;
|
||||
pipelineName?: string;
|
||||
};
|
||||
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 (reference) '../../../styles/variables.less';
|
||||
|
||||
.bundle-suite-form {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 80px; // Space for fixed buttons
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: @size-lg;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item-label > label {
|
||||
color: @grey-800;
|
||||
font-weight: @font-medium;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
.basic-info-card,
|
||||
.test-case-selection-card,
|
||||
.scheduler-card {
|
||||
background-color: @grey-1;
|
||||
border: 1px solid @grey-200;
|
||||
border-radius: @border-radius-sm;
|
||||
margin-bottom: @size-lg;
|
||||
|
||||
.ant-card-body {
|
||||
padding: @size-sm @size-mlg @size-mlg;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: @size-lg;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item-label > label {
|
||||
color: @grey-800;
|
||||
font-weight: @font-medium;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: @grey-800;
|
||||
font-size: @size-md;
|
||||
font-weight: @font-medium;
|
||||
margin-bottom: @size-mlg;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.scheduler-card {
|
||||
.selection-card {
|
||||
background-color: @white;
|
||||
|
||||
&.selected,
|
||||
&:hover {
|
||||
background-color: @blue-22;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bundle-suite-form-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: @white;
|
||||
border-top: 1px solid @grey-200;
|
||||
padding: @size-sm @size-mlg;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: @size-sm;
|
||||
z-index: 1000;
|
||||
|
||||
.ant-btn {
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
&.drawer-mode {
|
||||
min-height: auto;
|
||||
padding-bottom: 0;
|
||||
|
||||
.bundle-suite-form-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.standalone-mode {
|
||||
.drawer-footer-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.block-editor-wrapper.block-editor-wrapper--bar-menu .om-block-editor {
|
||||
background-color: @white;
|
||||
max-height: 150px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,459 @@
|
||||
/*
|
||||
* 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 {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Drawer,
|
||||
Form,
|
||||
Row,
|
||||
Space,
|
||||
Switch,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { useForm } from 'antd/lib/form/Form';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { DEFAULT_SCHEDULE_CRON_DAILY } from '../../../constants/Schedular.constants';
|
||||
import { useAirflowStatus } from '../../../context/AirflowStatusProvider/AirflowStatusProvider';
|
||||
import { useLimitStore } from '../../../context/LimitsProvider/useLimitsStore';
|
||||
import { OwnerType } from '../../../enums/user.enum';
|
||||
import {
|
||||
ConfigType,
|
||||
CreateIngestionPipeline,
|
||||
PipelineType,
|
||||
} from '../../../generated/api/services/ingestionPipelines/createIngestionPipeline';
|
||||
import { CreateTestSuite } from '../../../generated/api/tests/createTestSuite';
|
||||
import { LogLevels } from '../../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import { TestCase } from '../../../generated/tests/testCase';
|
||||
import { TestSuite } from '../../../generated/tests/testSuite';
|
||||
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||
import { FieldProp, FieldTypes } from '../../../interface/FormUtils.interface';
|
||||
import {
|
||||
addIngestionPipeline,
|
||||
deployIngestionPipelineById,
|
||||
} from '../../../rest/ingestionPipelineAPI';
|
||||
import {
|
||||
addTestCaseToLogicalTestSuite,
|
||||
createTestSuites,
|
||||
} from '../../../rest/testAPI';
|
||||
import {
|
||||
getNameFromFQN,
|
||||
replaceAllSpacialCharWith_,
|
||||
} from '../../../utils/CommonUtils';
|
||||
import { getEntityName } from '../../../utils/EntityUtils';
|
||||
import { generateFormFields } from '../../../utils/formUtils';
|
||||
import { getScheduleOptionsFromSchedules } from '../../../utils/SchedularUtils';
|
||||
import { getIngestionName } from '../../../utils/ServiceUtils';
|
||||
import { generateUUID } from '../../../utils/StringsUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
|
||||
import ScheduleIntervalV1 from '../../Settings/Services/AddIngestion/Steps/ScheduleIntervalV1';
|
||||
import { AddTestCaseList } from '../AddTestCaseList/AddTestCaseList.component';
|
||||
import {
|
||||
BundleSuiteFormData,
|
||||
BundleSuiteFormProps,
|
||||
} from './BundleSuiteForm.interface';
|
||||
import './BundleSuiteForm.less';
|
||||
|
||||
// =============================================
|
||||
// MAIN COMPONENT
|
||||
// =============================================
|
||||
const BundleSuiteForm: React.FC<BundleSuiteFormProps> = ({
|
||||
className,
|
||||
drawerProps,
|
||||
isDrawer = false,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
initialValues,
|
||||
}) => {
|
||||
// =============================================
|
||||
// HOOKS - External
|
||||
// =============================================
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [form] = useForm<BundleSuiteFormData>();
|
||||
const { config } = useLimitStore();
|
||||
const { currentUser } = useApplicationStore();
|
||||
const { isAirflowAvailable } = useAirflowStatus();
|
||||
|
||||
// =============================================
|
||||
// HOOKS - State
|
||||
// =============================================
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [selectedTestCases, setSelectedTestCases] = useState<TestCase[]>(
|
||||
initialValues?.testCases || []
|
||||
);
|
||||
|
||||
// =============================================
|
||||
// HOOKS - Memoized Values
|
||||
// =============================================
|
||||
const pipelineSchedules = useMemo(() => {
|
||||
return config?.limits?.config.featureLimits.find(
|
||||
(feature) => feature.name === 'dataQuality'
|
||||
)?.pipelineSchedules;
|
||||
}, [config]);
|
||||
|
||||
const schedulerOptions = useMemo(() => {
|
||||
if (isEmpty(pipelineSchedules) || !pipelineSchedules) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getScheduleOptionsFromSchedules(pipelineSchedules);
|
||||
}, [pipelineSchedules]);
|
||||
|
||||
// Form field definitions
|
||||
const basicInfoFormFields: FieldProp[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'name',
|
||||
label: t('label.name'),
|
||||
type: FieldTypes.TEXT,
|
||||
required: true,
|
||||
placeholder: t('label.enter-entity', { entity: t('label.name') }),
|
||||
props: { 'data-testid': 'test-suite-name' },
|
||||
id: 'root/name',
|
||||
rules: [
|
||||
{
|
||||
max: 256,
|
||||
message: t('message.entity-maximum-size', {
|
||||
entity: t('label.name'),
|
||||
max: 256,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: t('label.description'),
|
||||
type: FieldTypes.DESCRIPTION,
|
||||
required: false,
|
||||
placeholder: t('label.enter-entity', {
|
||||
entity: t('label.description'),
|
||||
}),
|
||||
props: { 'data-testid': 'test-suite-description', rows: 3 },
|
||||
id: 'root/description',
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const schedulerFormFields: FieldProp[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'pipelineName',
|
||||
label: t('label.name'),
|
||||
type: FieldTypes.TEXT,
|
||||
required: false,
|
||||
placeholder: t('label.enter-entity', { entity: t('label.name') }),
|
||||
props: { 'data-testid': 'pipeline-name' },
|
||||
id: 'root/pipelineName',
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
// =============================================
|
||||
// HOOKS - Effects
|
||||
// =============================================
|
||||
|
||||
// Initialize form values
|
||||
useEffect(() => {
|
||||
if (initialValues) {
|
||||
form.setFieldsValue({
|
||||
name: initialValues.name || '',
|
||||
description: initialValues.description || '',
|
||||
testCases: initialValues.testCases || [],
|
||||
});
|
||||
}
|
||||
}, [initialValues, form]);
|
||||
|
||||
// =============================================
|
||||
// HOOKS - Callbacks
|
||||
// =============================================
|
||||
const handleTestCaseSelection = useCallback(
|
||||
(testCases: TestCase[]) => {
|
||||
setSelectedTestCases(testCases);
|
||||
form.setFieldValue('testCases', testCases);
|
||||
},
|
||||
[form]
|
||||
);
|
||||
|
||||
const createAndDeployPipeline = async (
|
||||
testSuite: TestSuite,
|
||||
formData: BundleSuiteFormData
|
||||
) => {
|
||||
try {
|
||||
const testSuiteName = replaceAllSpacialCharWith_(
|
||||
getNameFromFQN(testSuite.fullyQualifiedName ?? testSuite.name)
|
||||
);
|
||||
const pipelineName =
|
||||
formData.pipelineName ||
|
||||
getIngestionName(testSuiteName, PipelineType.TestSuite);
|
||||
|
||||
const ingestionPayload: CreateIngestionPipeline = {
|
||||
airflowConfig: {
|
||||
scheduleInterval: formData.cron,
|
||||
},
|
||||
displayName: pipelineName,
|
||||
name: generateUUID(),
|
||||
loggerLevel: formData.enableDebugLog ? LogLevels.Debug : LogLevels.Info,
|
||||
pipelineType: PipelineType.TestSuite,
|
||||
raiseOnError: formData.raiseOnError ?? true,
|
||||
service: {
|
||||
id: testSuite.id ?? '',
|
||||
type: 'testSuite',
|
||||
},
|
||||
sourceConfig: {
|
||||
config: {
|
||||
type: ConfigType.TestSuite,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const pipeline = await addIngestionPipeline(ingestionPayload);
|
||||
|
||||
if (isAirflowAvailable) {
|
||||
await deployIngestionPipelineById(pipeline.id ?? '');
|
||||
}
|
||||
|
||||
showSuccessToast(
|
||||
t('message.pipeline-deployed-successfully', {
|
||||
pipelineName: getEntityName(pipeline),
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.create-entity-error', {
|
||||
entity: t('label.pipeline'),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const createTestSuiteWithPipeline = async (formData: BundleSuiteFormData) => {
|
||||
const testSuitePayload: CreateTestSuite = {
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
owners: currentUser?.id
|
||||
? [{ id: currentUser.id, type: OwnerType.USER }]
|
||||
: [],
|
||||
};
|
||||
|
||||
const testSuite = await createTestSuites(testSuitePayload);
|
||||
|
||||
await addTestCaseToLogicalTestSuite({
|
||||
testCaseIds: selectedTestCases.map((testCase) => testCase.id ?? ''),
|
||||
testSuiteId: testSuite.id ?? '',
|
||||
});
|
||||
|
||||
if (formData.cron) {
|
||||
await createAndDeployPipeline(testSuite, formData);
|
||||
}
|
||||
|
||||
return testSuite;
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (values: BundleSuiteFormData) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const formData = {
|
||||
...values,
|
||||
testCases: selectedTestCases,
|
||||
};
|
||||
|
||||
const testSuite = await createTestSuiteWithPipeline(formData);
|
||||
|
||||
onSuccess?.(testSuite);
|
||||
|
||||
showSuccessToast(
|
||||
t('message.entity-created-successfully', {
|
||||
entity: t('label.test-suite'),
|
||||
})
|
||||
);
|
||||
|
||||
if (isDrawer) {
|
||||
onCancel?.();
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.create-entity-error', {
|
||||
entity: t('label.test-suite'),
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
} else {
|
||||
navigate(-1);
|
||||
}
|
||||
};
|
||||
|
||||
const renderActionButtons = (
|
||||
<Space size={16}>
|
||||
<Button data-testid="cancel-button" onClick={handleCancel}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="submit-button"
|
||||
form="bundle-suite-form"
|
||||
htmlType="submit"
|
||||
loading={isSubmitting}
|
||||
type="primary">
|
||||
{t('label.create')}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
|
||||
const formContent = (
|
||||
<div
|
||||
className={classNames(
|
||||
'bundle-suite-form',
|
||||
{
|
||||
'drawer-mode': isDrawer,
|
||||
'standalone-mode': !isDrawer,
|
||||
},
|
||||
className
|
||||
)}>
|
||||
<Form
|
||||
form={form}
|
||||
id="bundle-suite-form"
|
||||
initialValues={{
|
||||
raiseOnError: true,
|
||||
cron: DEFAULT_SCHEDULE_CRON_DAILY,
|
||||
enableDebugLog: false,
|
||||
...initialValues,
|
||||
}}
|
||||
layout="vertical"
|
||||
onFinish={handleFormSubmit}>
|
||||
{!isDrawer && (
|
||||
<Typography.Title level={4}>
|
||||
{t('label.create-entity', { entity: t('label.bundle-suite') })}
|
||||
</Typography.Title>
|
||||
)}
|
||||
|
||||
{/* Basic Information */}
|
||||
<Card className="basic-info-card">
|
||||
{generateFormFields(basicInfoFormFields)}
|
||||
</Card>
|
||||
|
||||
{/* Test Case Selection */}
|
||||
<Card className="test-case-selection-card">
|
||||
<Form.Item
|
||||
label={t('label.test-case-plural')}
|
||||
name="testCases"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t('label.field-required', {
|
||||
field: t('label.test-case-plural'),
|
||||
}),
|
||||
},
|
||||
]}>
|
||||
<AddTestCaseList
|
||||
selectedTest={selectedTestCases.map((tc) => tc.name)}
|
||||
showButton={false}
|
||||
onChange={handleTestCaseSelection}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
{/* Scheduler - Always Visible */}
|
||||
<Card className="scheduler-card">
|
||||
<div className="card-title">
|
||||
{t('label.schedule-for-entity', {
|
||||
entity: t('label.test-suite'),
|
||||
})}
|
||||
</div>
|
||||
|
||||
{generateFormFields(schedulerFormFields)}
|
||||
|
||||
<Form.Item label={t('label.schedule-interval')} name="cron">
|
||||
<ScheduleIntervalV1 includePeriodOptions={schedulerOptions} />
|
||||
</Form.Item>
|
||||
|
||||
{/* Debug Log and Raise on Error switches */}
|
||||
<div style={{ marginTop: '24px' }}>
|
||||
<Row gutter={[24, 16]}>
|
||||
<Col span={12}>
|
||||
<div className="d-flex justify-between align-center">
|
||||
<Typography.Text className="font-medium">
|
||||
{t('label.enable-debug-log')}
|
||||
</Typography.Text>
|
||||
<Form.Item
|
||||
name="enableDebugLog"
|
||||
style={{ marginBottom: 0 }}
|
||||
valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="d-flex justify-between align-center">
|
||||
<Typography.Text className="font-medium">
|
||||
{t('label.raise-on-error')}
|
||||
</Typography.Text>
|
||||
<Form.Item
|
||||
name="raiseOnError"
|
||||
style={{ marginBottom: 0 }}
|
||||
valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Card>
|
||||
</Form>
|
||||
|
||||
{!isDrawer && (
|
||||
<div className="bundle-suite-form-actions">{renderActionButtons}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const drawerFooter = (
|
||||
<div className="drawer-footer-actions">{renderActionButtons}</div>
|
||||
);
|
||||
|
||||
if (isDrawer) {
|
||||
return (
|
||||
<Drawer
|
||||
destroyOnClose
|
||||
closable={false}
|
||||
footer={drawerFooter}
|
||||
maskClosable={false}
|
||||
placement="right"
|
||||
size="large"
|
||||
{...drawerProps}
|
||||
onClose={onCancel}>
|
||||
<div className="drawer-form-content">{formContent}</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
return formContent;
|
||||
};
|
||||
|
||||
export default BundleSuiteForm;
|
||||
@ -15,12 +15,12 @@ import { Button, Card, Col, Row, Tabs } from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import ManageButton from '../../components/common/EntityPageInfos/ManageButton/ManageButton';
|
||||
import TabsLabel from '../../components/common/TabsLabel/TabsLabel.component';
|
||||
import TestCaseFormV1 from '../../components/DataQuality/AddDataQualityTest/components/TestCaseFormV1';
|
||||
import BundleSuiteForm from '../../components/DataQuality/BundleSuiteForm/BundleSuiteForm';
|
||||
import PageHeader from '../../components/PageHeader/PageHeader.component';
|
||||
import { ROUTES } from '../../constants/constants';
|
||||
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { withPageLayout } from '../../hoc/withPageLayout';
|
||||
@ -41,6 +41,7 @@ const DataQualityPage = () => {
|
||||
|
||||
// Add state for modal open/close
|
||||
const [isTestCaseModalOpen, setIsTestCaseModalOpen] = useState(false);
|
||||
const [isBundleSuiteModalOpen, setIsBundleSuiteModalOpen] = useState(false);
|
||||
|
||||
// Add handlers for modal
|
||||
const handleOpenTestCaseModal = () => {
|
||||
@ -51,6 +52,14 @@ const DataQualityPage = () => {
|
||||
setIsTestCaseModalOpen(false);
|
||||
};
|
||||
|
||||
const handleOpenBundleSuiteModal = () => {
|
||||
setIsBundleSuiteModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseBundleSuiteModal = () => {
|
||||
setIsBundleSuiteModalOpen(false);
|
||||
};
|
||||
|
||||
const menuItems = useMemo(() => {
|
||||
const data = DataQualityClassBase.getDataQualityTab();
|
||||
|
||||
@ -94,15 +103,14 @@ const DataQualityPage = () => {
|
||||
<Col className="d-flex justify-end" span={8}>
|
||||
{activeTab === DataQualityPageTabs.TEST_SUITES &&
|
||||
testSuitePermission?.Create && (
|
||||
<Link
|
||||
<Button
|
||||
data-testid="add-test-suite-btn"
|
||||
to={ROUTES.ADD_TEST_SUITES}>
|
||||
<Button type="primary">
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.bundle-suite-plural'),
|
||||
})}
|
||||
</Button>
|
||||
</Link>
|
||||
type="primary"
|
||||
onClick={handleOpenBundleSuiteModal}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.bundle-suite-plural'),
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
{activeTab === DataQualityPageTabs.TEST_CASES &&
|
||||
testSuitePermission?.Create && (
|
||||
@ -136,16 +144,31 @@ const DataQualityPage = () => {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<TestCaseFormV1
|
||||
isDrawer
|
||||
drawerProps={{
|
||||
title: t('label.add-entity', {
|
||||
entity: t('label.test-case'),
|
||||
}),
|
||||
open: isTestCaseModalOpen,
|
||||
}}
|
||||
onCancel={handleCloseTestCaseModal}
|
||||
/>
|
||||
{isTestCaseModalOpen && (
|
||||
<TestCaseFormV1
|
||||
isDrawer
|
||||
drawerProps={{
|
||||
title: t('label.add-entity', {
|
||||
entity: t('label.test-case'),
|
||||
}),
|
||||
open: isTestCaseModalOpen,
|
||||
}}
|
||||
onCancel={handleCloseTestCaseModal}
|
||||
/>
|
||||
)}
|
||||
{isBundleSuiteModalOpen && (
|
||||
<BundleSuiteForm
|
||||
isDrawer
|
||||
drawerProps={{
|
||||
title: t('label.add-entity', {
|
||||
entity: t('label.bundle-suite-plural'),
|
||||
}),
|
||||
open: isBundleSuiteModalOpen,
|
||||
}}
|
||||
onCancel={handleCloseBundleSuiteModal}
|
||||
onSuccess={handleCloseBundleSuiteModal}
|
||||
/>
|
||||
)}
|
||||
</DataQualityProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user