mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-05 04:56:54 +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';
|
} from '../../../../constants/constants';
|
||||||
import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants';
|
import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants';
|
||||||
import { DEFAULT_SCHEDULE_CRON_DAILY } from '../../../../constants/Schedular.constants';
|
import { DEFAULT_SCHEDULE_CRON_DAILY } from '../../../../constants/Schedular.constants';
|
||||||
|
import { useAirflowStatus } from '../../../../context/AirflowStatusProvider/AirflowStatusProvider';
|
||||||
import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore';
|
import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore';
|
||||||
import { SearchIndex } from '../../../../enums/search.enum';
|
import { SearchIndex } from '../../../../enums/search.enum';
|
||||||
import { TagSource } from '../../../../generated/api/domains/createDataProduct';
|
import { TagSource } from '../../../../generated/api/domains/createDataProduct';
|
||||||
@ -161,6 +162,7 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { config } = useLimitStore();
|
const { config } = useLimitStore();
|
||||||
const [form] = useForm<FormValues>();
|
const [form] = useForm<FormValues>();
|
||||||
|
const { isAirflowAvailable } = useAirflowStatus();
|
||||||
|
|
||||||
// =============================================
|
// =============================================
|
||||||
// HOOKS - State (grouped by functionality)
|
// HOOKS - State (grouped by functionality)
|
||||||
@ -759,7 +761,9 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ingestion = await addIngestionPipeline(ingestionPayload);
|
const ingestion = await addIngestionPipeline(ingestionPayload);
|
||||||
await deployIngestionPipelineById(ingestion.id ?? '');
|
if (isAirflowAvailable) {
|
||||||
|
await deployIngestionPipelineById(ingestion.id ?? '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showSuccessToast(
|
showSuccessToast(
|
||||||
@ -959,7 +963,7 @@ const TestCaseFormV1: FC<TestCaseFormV1Props> = ({
|
|||||||
initialValues={{
|
initialValues={{
|
||||||
testLevel: TestLevel.TABLE,
|
testLevel: TestLevel.TABLE,
|
||||||
...testCaseClassBase.initialFormValues(),
|
...testCaseClassBase.initialFormValues(),
|
||||||
testName: replaceAllSpacialCharWith_(initialValues?.testName ?? ''),
|
testName: initialValues?.testName,
|
||||||
testTypeId: initialValues?.testTypeId,
|
testTypeId: initialValues?.testTypeId,
|
||||||
params: getInitialParamsValue,
|
params: getInitialParamsValue,
|
||||||
tags: initialValues?.tags || [],
|
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 { isEmpty } from 'lodash';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 ManageButton from '../../components/common/EntityPageInfos/ManageButton/ManageButton';
|
||||||
import TabsLabel from '../../components/common/TabsLabel/TabsLabel.component';
|
import TabsLabel from '../../components/common/TabsLabel/TabsLabel.component';
|
||||||
import TestCaseFormV1 from '../../components/DataQuality/AddDataQualityTest/components/TestCaseFormV1';
|
import TestCaseFormV1 from '../../components/DataQuality/AddDataQualityTest/components/TestCaseFormV1';
|
||||||
|
import BundleSuiteForm from '../../components/DataQuality/BundleSuiteForm/BundleSuiteForm';
|
||||||
import PageHeader from '../../components/PageHeader/PageHeader.component';
|
import PageHeader from '../../components/PageHeader/PageHeader.component';
|
||||||
import { ROUTES } from '../../constants/constants';
|
|
||||||
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
|
import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider';
|
||||||
import { EntityType } from '../../enums/entity.enum';
|
import { EntityType } from '../../enums/entity.enum';
|
||||||
import { withPageLayout } from '../../hoc/withPageLayout';
|
import { withPageLayout } from '../../hoc/withPageLayout';
|
||||||
@ -41,6 +41,7 @@ const DataQualityPage = () => {
|
|||||||
|
|
||||||
// Add state for modal open/close
|
// Add state for modal open/close
|
||||||
const [isTestCaseModalOpen, setIsTestCaseModalOpen] = useState(false);
|
const [isTestCaseModalOpen, setIsTestCaseModalOpen] = useState(false);
|
||||||
|
const [isBundleSuiteModalOpen, setIsBundleSuiteModalOpen] = useState(false);
|
||||||
|
|
||||||
// Add handlers for modal
|
// Add handlers for modal
|
||||||
const handleOpenTestCaseModal = () => {
|
const handleOpenTestCaseModal = () => {
|
||||||
@ -51,6 +52,14 @@ const DataQualityPage = () => {
|
|||||||
setIsTestCaseModalOpen(false);
|
setIsTestCaseModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenBundleSuiteModal = () => {
|
||||||
|
setIsBundleSuiteModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseBundleSuiteModal = () => {
|
||||||
|
setIsBundleSuiteModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const menuItems = useMemo(() => {
|
const menuItems = useMemo(() => {
|
||||||
const data = DataQualityClassBase.getDataQualityTab();
|
const data = DataQualityClassBase.getDataQualityTab();
|
||||||
|
|
||||||
@ -94,15 +103,14 @@ const DataQualityPage = () => {
|
|||||||
<Col className="d-flex justify-end" span={8}>
|
<Col className="d-flex justify-end" span={8}>
|
||||||
{activeTab === DataQualityPageTabs.TEST_SUITES &&
|
{activeTab === DataQualityPageTabs.TEST_SUITES &&
|
||||||
testSuitePermission?.Create && (
|
testSuitePermission?.Create && (
|
||||||
<Link
|
<Button
|
||||||
data-testid="add-test-suite-btn"
|
data-testid="add-test-suite-btn"
|
||||||
to={ROUTES.ADD_TEST_SUITES}>
|
type="primary"
|
||||||
<Button type="primary">
|
onClick={handleOpenBundleSuiteModal}>
|
||||||
{t('label.add-entity', {
|
{t('label.add-entity', {
|
||||||
entity: t('label.bundle-suite-plural'),
|
entity: t('label.bundle-suite-plural'),
|
||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
|
||||||
)}
|
)}
|
||||||
{activeTab === DataQualityPageTabs.TEST_CASES &&
|
{activeTab === DataQualityPageTabs.TEST_CASES &&
|
||||||
testSuitePermission?.Create && (
|
testSuitePermission?.Create && (
|
||||||
@ -136,16 +144,31 @@ const DataQualityPage = () => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<TestCaseFormV1
|
{isTestCaseModalOpen && (
|
||||||
isDrawer
|
<TestCaseFormV1
|
||||||
drawerProps={{
|
isDrawer
|
||||||
title: t('label.add-entity', {
|
drawerProps={{
|
||||||
entity: t('label.test-case'),
|
title: t('label.add-entity', {
|
||||||
}),
|
entity: t('label.test-case'),
|
||||||
open: isTestCaseModalOpen,
|
}),
|
||||||
}}
|
open: isTestCaseModalOpen,
|
||||||
onCancel={handleCloseTestCaseModal}
|
}}
|
||||||
/>
|
onCancel={handleCloseTestCaseModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isBundleSuiteModalOpen && (
|
||||||
|
<BundleSuiteForm
|
||||||
|
isDrawer
|
||||||
|
drawerProps={{
|
||||||
|
title: t('label.add-entity', {
|
||||||
|
entity: t('label.bundle-suite-plural'),
|
||||||
|
}),
|
||||||
|
open: isBundleSuiteModalOpen,
|
||||||
|
}}
|
||||||
|
onCancel={handleCloseBundleSuiteModal}
|
||||||
|
onSuccess={handleCloseBundleSuiteModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DataQualityProvider>
|
</DataQualityProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user