blob: ce6eadf6638f57c11ff682752ea3b0a4b4b0b648 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 { createEditBusinessMetadata } from "@api/apiMethods/typeDefApiMethods";
import { CustomButton, LightTooltip } from "@components/muiComponents";
import Button from "@mui/material/Button";
import CardActions from "@mui/material/CardActions";
import CardHeader from "@mui/material/CardHeader";
import Card from "@mui/material/Card";
import InputLabel from "@mui/material/InputLabel";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid";
import Divider from "@mui/material/Divider";
import CircularProgress from "@mui/material/CircularProgress";
import CardContent from "@mui/material/CardContent";
import { isEmpty, serverError } from "@utils/Utils";
import { useEffect, useRef, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import ReactQuill from "react-quill-new";
import { toast } from "react-toastify";
import AddIcon from "@mui/icons-material/Add";
import { useAppDispatch, useAppSelector } from "@hooks/reducerHook";
import BusinessMetadataAttributeForm from "./BusinessMetadataAtrributeForm";
import { setEditBMAttribute } from "@redux/slice/createBMSlice";
import { cloneDeep } from "@utils/Helper";
import { fetchBusinessMetaData } from "@redux/slice/typeDefSlices/typedefBusinessMetadataSlice";
import { fetchEntityData } from "@redux/slice/typeDefSlices/typedefEntitySlice";
import { defaultType } from "@utils/Enum";
import { getTypeName } from "@utils/CommonViewFunction";
const BusinessMetaDataForm = ({
setForm,
setBMAttribute,
bmAttribute
}: any) => {
const { editbmAttribute }: any = useAppSelector((state) => state.createBM);
const toastId: any = useRef(null);
const [alignment, setAlignment] = useState<string>("formatted");
const { typeHeaderData } = useAppSelector((state: any) => state.typeHeader);
const { enumObj }: any = useAppSelector((state: any) => state.enum);
const { enumDefs } = enumObj.data || {};
let enumTypes = !isEmpty(enumDefs)
? enumDefs.map((obj: { name: any }) => {
return obj.name;
})
: [];
const { name }: any = bmAttribute;
const { name: editAttrName, typeName, options }: any = editbmAttribute;
const str = !isEmpty(typeName) ? typeName : "";
const start = !isEmpty(str) ? str.indexOf("<") + 1 : "";
const end = !isEmpty(str) ? str.indexOf(">") : "";
const extracted = !isEmpty(str) ? str.slice(start, end) : "";
let currentTypeName =
str.indexOf("<") != -1
? defaultType.includes(typeName)
? extracted
: "enumeration"
: defaultType.includes(typeName)
? typeName
: "enumeration";
let selectedEnumObj = !isEmpty(enumDefs)
? enumDefs.find((obj: { name: any }) => {
return obj.name == (str.indexOf("<") != -1 ? extracted : str);
})
: {};
let selectedEnumValues = !isEmpty(selectedEnumObj)
? selectedEnumObj?.elementDefs
: [];
let enumTypeOptions = [...selectedEnumValues];
const editDefaultObj = [
{
...(editbmAttribute || {}),
options: {
...(options || {}),
applicableEntityTypes: !isEmpty(options)
? (function () {
try {
return JSON.parse(options?.applicableEntityTypes);
} catch (e) {
return options?.applicableEntityTypes;
}
})()
: []
},
typeName:
str.indexOf("<") != -1
? defaultType.includes(typeName)
? extracted
: "enumeration"
: defaultType.includes(typeName)
? typeName
: "enumeration",
...(currentTypeName == "enumeration" && {
enumType: str.indexOf("<") != -1 ? extracted : str
}),
...(currentTypeName == "enumeration" && {
enumValues: enumTypeOptions
}),
multiValueSelect: str.indexOf("<") != -1 ? true : false,
// Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET
cardinalityToggle: editbmAttribute?.cardinality === "LIST" ? "LIST" : "SET"
}
];
const dispatchState = useAppDispatch();
const {
control,
handleSubmit,
setValue,
watch,
reset,
formState: { isSubmitting }
} = useForm<any>({
...((!isEmpty(bmAttribute) || !isEmpty(editbmAttribute)) && {
defaultValues: {
attributeDefs: [
!isEmpty(bmAttribute) && isEmpty(editbmAttribute)
? {
name: "",
typeName: "string",
searchWeight: 5,
multiValueSelect: false,
options: {
maxStrLength: 50,
applicableEntityTypes: null
},
isOptional: true,
cardinality: "SINGLE",
cardinalityToggle: "SET", // Default toggle value when multivalues is enabled
valuesMinCount: 0,
valuesMaxCount: 1,
isUnique: false,
isIndexable: true
}
: editDefaultObj
]
}
})
});
const { fields, append, remove } = useFieldArray({
control,
name: "attributeDefs",
rules: {
required: false // Allow creating BM without attributes
}
});
useEffect(() => {
let currentTypeName =
str.indexOf("<") != -1
? defaultType.includes(typeName)
? extracted
: "enumeration"
: defaultType.includes(typeName)
? typeName
: "enumeration";
let selectedEnumObj = !isEmpty(enumDefs)
? enumDefs.find((obj: { name: any }) => {
return obj.name == (str.indexOf("<") != -1 ? extracted : str);
})
: {};
let selectedEnumValues = !isEmpty(selectedEnumObj)
? selectedEnumObj?.elementDefs
: [];
let enumTypeOptions = [...selectedEnumValues];
const editObj = [
{
...(editbmAttribute || {}),
options: {
...(options || {}),
applicableEntityTypes: !isEmpty(options)
? (function () {
try {
return JSON.parse(options?.applicableEntityTypes);
} catch (e) {
return options?.applicableEntityTypes;
}
})()
: []
},
typeName: currentTypeName,
...(currentTypeName == "enumeration" && {
enumType: str.indexOf("<") != -1 ? extracted : str
}),
...(currentTypeName == "enumeration" && {
enumValues: enumTypeOptions
}),
multiValueSelect: str.indexOf("<") != -1 ? true : false,
// Set cardinalityToggle based on existing cardinality (SET or LIST), default to SET
cardinalityToggle: editbmAttribute?.cardinality === "LIST" ? "LIST" : "SET"
}
];
if (!isEmpty(editbmAttribute)) {
reset({
attributeDefs: editObj || []
});
}
}, [editbmAttribute]);
const dataTypeOptions = !isEmpty(typeHeaderData)
? typeHeaderData
.map((obj: { category: string; name: any }) => {
if (obj.category == "ENTITY") {
return obj.name;
}
})
.filter(Boolean)
: [];
const watched = watch("attributeDefs" as any);
const handleChange = (
event: React.MouseEvent<HTMLElement>,
newAlignment: string
) => {
event?.stopPropagation();
setAlignment(newAlignment);
};
const toastMssg = (bmName: string) => {
if (isEmpty(bmAttribute) && isEmpty(editbmAttribute)) {
toast.success(`Business Metadata ${bmName} was created successfully`);
} else {
toast.success(
"One or more Business Metadata attributes were updated successfully"
);
}
};
const onSubmit = async (values: any) => {
let formData = { ...values };
let bmData = cloneDeep(bmAttribute);
const { name, description, attributeDefs } = formData;
if (isEmpty(bmAttribute) && isEmpty(editAttrName) && isEmpty(name)) {
toast.info("Please enter the Enumeration name");
return;
}
let attributeDefsData = !isEmpty(attributeDefs) ? [...attributeDefs] : [];
let attributes = !isEmpty(attributeDefsData)
? attributeDefsData.map((item) => {
const { multiValueSelect, enumType, enumValues, cardinality, cardinalityToggle, ...rest } = item;
// Determine cardinality based on multiValueSelect and cardinality toggle
let finalCardinality = "SINGLE";
if (multiValueSelect) {
// If multivalues is enabled, use the cardinalityToggle (SET or LIST)
// Default to SET if not specified
finalCardinality = cardinalityToggle === "LIST" ? "LIST" : (cardinality === "LIST" ? "LIST" : "SET");
}
const baseObj = {
...rest,
cardinality: finalCardinality,
options: {
applicableEntityTypes: JSON.stringify(
rest.options.applicableEntityTypes
),
maxStrLength: rest.options.maxStrLength
},
...(multiValueSelect && {
multiValueSelect: true,
multiValued: true
}),
...(enumType && {
enumValues: !isEmpty(enumValues)
? enumValues.map((enums: { value: any }) => {
return enums.value;
})
: []
}),
typeName: getTypeName(multiValueSelect, enumType, rest)
};
return baseObj;
})
: [];
let data = {
structDefs: [],
enumDefs: [],
classificationDefs: [],
entityDefs: [],
businessMetadataDefs: [
isEmpty(bmAttribute) && isEmpty(editbmAttribute)
? {
category: "BUSINESS_METADATA",
createdBy: "admin",
updatedBy: "admin",
version: 1,
typeVersion: "1.1",
name: name.trim(),
description: description ? description.trim() : "",
attributeDefs: attributes
}
: !isEmpty(bmAttribute) && isEmpty(editbmAttribute)
? {
...bmAttribute,
attributeDefs: [...bmAttribute.attributeDefs, ...attributes]
}
: {
...bmData,
attributeDefs: bmData?.attributeDefs?.map((obj: any) => {
const matchingAttribute = attributes.find(
(attr: any) => attr.name === obj.name
);
return matchingAttribute ? matchingAttribute : obj;
})
}
]
};
try {
const response = await createEditBusinessMetadata(
"business_metadata",
isEmpty(bmAttribute) && isEmpty(editAttrName) ? "POST" : "PUT",
data
);
let bmName = response?.data?.businessMetadataDefs?.[0]?.name;
toastMssg(bmName);
dispatchState(fetchBusinessMetaData());
dispatchState(fetchEntityData());
setBMAttribute({});
setForm(false);
} catch (error) {
serverError(error, toastId);
}
};
const bmTitle = () => {
if (isEmpty(bmAttribute) && isEmpty(editbmAttribute)) {
return "Create Business Metadata";
} else if (!isEmpty(bmAttribute) && isEmpty(editbmAttribute)) {
return `Add Business Metadata Attribute for:
${name}`;
} else if (!isEmpty(editbmAttribute)) {
return `Update Attribute of: ${editAttrName}`;
}
};
return (
<>
<Card sx={{ border: 0, height: "100%" }} variant="outlined">
<CardHeader
title={
<>
<Typography textAlign="left" fontSize={18} fontWeight={600}>
{bmTitle()}
</Typography>
<Divider />
</>
}
/>
<CardContent sx={{ textAlign: "left" }}>
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
{isEmpty(bmAttribute) && isEmpty(editbmAttribute) && (
<>
<Controller
control={control}
name={"name"}
rules={{
required: true
}}
render={({
field: { onChange, value },
fieldState: { error }
}) => (
<>
<Grid container spacing={2}>
<Grid
textAlign="right"
alignItems="center"
item
md={3}
>
<InputLabel className="form-textfield" required>
Name
</InputLabel>
</Grid>
<Grid item md={6}>
<TextField
margin="normal"
error={!!error}
fullWidth
value={value}
onChange={(e) => {
const value = e.target.value;
onChange(value);
}}
variant="outlined"
size="small"
placeholder={"Name required"}
className="form-textfield"
/>
</Grid>
</Grid>
</>
)}
/>
<Controller
control={control}
name={"description"}
render={({ field }) => (
<>
<Grid container spacing={2}>
<Grid item textAlign="right" md={3}>
<InputLabel>Description</InputLabel>
</Grid>
<Grid item md={6}>
<Stack gap={1} marginBottom="1.5rem">
<ToggleButtonGroup
size="small"
color="primary"
value={alignment}
exclusive
onChange={(e, newValue) => {
e.stopPropagation();
handleChange(e, newValue);
}}
aria-label="Platform"
>
<ToggleButton
className="entity-form-toggle-btn"
value="formatted"
data-cy="formatted"
>
Formatted Text
</ToggleButton>
<ToggleButton
value="plain"
className="entity-form-toggle-btn"
data-cy="plain"
>
Plain text
</ToggleButton>
</ToggleButtonGroup>
{alignment == "formatted" ? (
<div style={{ position: "relative" }}>
<ReactQuill
theme="snow"
placeholder={"Description required"}
onChange={(text) => {
field.onChange(text);
setValue("description", text);
}}
className="classification-form-editor"
value={field.value || ""}
/>
</div>
) : (
<textarea
{...field}
placeholder={"Long Description"}
onChange={(e) => {
e.stopPropagation();
const value = e.target.value;
field.onChange(value);
setValue("description", value);
}}
style={{ width: "100%" }}
className="form-textfield form-textarea-field"
/>
)}
</Stack>
</Grid>
</Grid>
</>
)}
/>
</>
)}
<Grid container spacing={2}>
<Grid item md={3}></Grid>
{isEmpty(editAttrName) && (
<Grid item md={6}>
<Stack marginBottom="1rem">
<LightTooltip title={"Add Business Metadata Attribute"}>
<CustomButton
sx={{
alignSelf: "flex-start",
marginBottom: "1rem",
height: "32px"
}}
variant="outlined"
size="small"
onClick={(e: any) => {
e.stopPropagation();
append({
name: "",
typeName: "string",
searchWeight: 5,
multiValueSelect: false,
options: {
maxStrLength: 50,
applicableEntityTypes: null
},
isOptional: true,
cardinality: "SINGLE",
cardinalityToggle: "SET", // Default toggle value when multivalues is enabled
valuesMinCount: 0,
valuesMaxCount: 1,
isUnique: false,
isIndexable: true
});
}}
startIcon={<AddIcon />}
>
Add Business Metadata Attribute
</CustomButton>
</LightTooltip>
</Stack>
</Grid>
)}
</Grid>
<Grid container gap={2} justifyContent="center">
<Grid item md={9}>
<Stack>
<BusinessMetadataAttributeForm
fields={fields}
control={control}
remove={remove}
watched={watched}
dataTypeOptions={dataTypeOptions}
enumTypes={enumTypes}
watch={watch}
setValue={setValue}
/>
</Stack>
</Grid>
</Grid>
</Grid>
<Divider />
<Stack
direction="row"
gap={2}
justifyContent="center"
paddingTop="1rem"
>
<CustomButton
variant="outlined"
color="primary"
onClick={(_e: Event) => {
setForm(false);
setBMAttribute({});
dispatchState(setEditBMAttribute({}));
}}
>
Cancel
</CustomButton>
<Button
startIcon={
isSubmitting && (
<CircularProgress
color="success"
sx={{ fontWeight: "600" }}
size="20px"
/>
)
}
type="submit"
variant="contained"
color="primary"
disabled={isSubmitting ? true : false}
>
{isEmpty(bmAttribute) && isEmpty(editAttrName)
? "Create"
: "Save"}
</Button>
</Stack>
</form>
</CardContent>
<CardActions disableSpacing></CardActions>
</Card>
</>
);
};
export default BusinessMetaDataForm;