This is an automated email from the ASF dual-hosted git repository.

pawarprasad123 pushed a commit to branch ATLAS-5371
in repository https://gitbox.apache.org/repos/asf/atlas.git

commit 31010f9971d8146deb794255fe63782e63481a6b
Author: Prasad Pawar <[email protected]>
AuthorDate: Mon Aug 10 22:17:32 2026 +0530

    ATLAS-5371: Atlas UI: Render ENUM-typed entity attributes as dropdowns in 
Create/Edit Entity form
---
 .../src/components/Forms/FormEnumMultiSelect.tsx   | 107 ++++++++
 .../Forms/__tests__/FormEnumMultiSelect.test.tsx   | 103 ++++++++
 .../utils/__tests__/entityTypeConfigUtils.test.ts  | 100 ++++++++
 .../src/utils/__tests__/enumTypeUtils.test.ts      | 158 ++++++++++++
 dashboard/src/utils/entityTypeConfigUtils.ts       |  62 +++++
 dashboard/src/utils/enumTypeUtils.ts               | 137 ++++++++++
 .../EntityDetailTabs/AttributeProperties.tsx       |  28 +-
 .../__tests__/AttributeProperties.test.tsx         |  64 +++++
 dashboard/src/views/Entity/EntityForm.tsx          |  93 +++----
 dashboard/src/views/Entity/entityFormFields.tsx    | 108 ++++++++
 dashboard/src/views/__tests__/EntityForm.test.tsx  | 105 +++++++-
 .../src/views/__tests__/EntityFormEnum.test.tsx    | 285 +++++++++++++++++++++
 .../js/views/entity/CreateEntityLayoutView.js      |  98 ++++++-
 .../js/views/entity/EntityDetailTableLayoutView.js |   3 +-
 .../js/views/search/SearchResultLayoutView.js      |   1 +
 15 files changed, 1371 insertions(+), 81 deletions(-)

diff --git a/dashboard/src/components/Forms/FormEnumMultiSelect.tsx 
b/dashboard/src/components/Forms/FormEnumMultiSelect.tsx
new file mode 100644
index 000000000..7beed0a0c
--- /dev/null
+++ b/dashboard/src/components/Forms/FormEnumMultiSelect.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 { LightTooltip } from "@components/muiComponents";
+import { Autocomplete, InputLabel, TextField, Typography } from 
"@mui/material";
+import {
+       areEnumOptionsEqual,
+       EnumOption,
+       getEnumOptionLabel,
+       normalizeMultiEnumValue
+} from "@utils/enumTypeUtils";
+import { Capitalize, isEmpty } from "@utils/Utils";
+import { Controller } from "react-hook-form";
+
+const FormEnumMultiSelect = ({
+       data,
+       control,
+       optionsList,
+       fieldName
+}: {
+       data: { name: string; isOptional: boolean; typeName: string; 
cardinality?: string };
+       control: any;
+       optionsList: EnumOption[];
+       fieldName?: string;
+}) => {
+       const { name, isOptional, typeName, cardinality } = data;
+
+       return (
+               <Controller
+                       name={!isEmpty(fieldName) ? `${fieldName}.${name}` : 
name}
+                       control={control}
+                       key={`enum-multi-${name}`}
+                       rules={{
+                               required: isOptional ? false : true
+                       }}
+                       defaultValue={[]}
+                       render={({ field: { onChange, value }, fieldState: { 
error } }) => {
+                               const selectedValues = 
normalizeMultiEnumValue(value, optionsList);
+
+                               return (
+                                       <>
+                                               <div className="form-fields">
+                                                       <InputLabel
+                                                               
className="form-textfield-label"
+                                                               
required={isOptional ? false : true}
+                                                       >
+                                                               
{Capitalize(name)}
+                                                       </InputLabel>
+                                                       <LightTooltip 
title={`Data Type: (${typeName})`}>
+                                                               <Typography
+                                                                       
color="#666666"
+                                                                       
overflow="hidden"
+                                                                       
maxWidth="160px"
+                                                                       
fontSize={14}
+                                                                       noWrap
+                                                               
>{`(${typeName})${cardinality ? ` ${cardinality}` : ""}`}</Typography>
+                                                       </LightTooltip>
+                                               </div>
+                                               <Autocomplete
+                                                       size="small"
+                                                       multiple
+                                                       disableCloseOnSelect
+                                                       
className="form-autocomplete-field"
+                                                       onChange={(_event, 
selectedOptions) => {
+                                                               
onChange(selectedOptions);
+                                                       }}
+                                                       sx={{ width: "100%" }}
+                                                       value={selectedValues}
+                                                       filterSelectedOptions
+                                                       
getOptionLabel={getEnumOptionLabel}
+                                                       
isOptionEqualToValue={areEnumOptionsEqual}
+                                                       options={optionsList}
+                                                       renderInput={(params) 
=> (
+                                                               <TextField
+                                                                       
{...params}
+                                                                       
error={!!error}
+                                                                       
className="form-textfield"
+                                                                       
size="small"
+                                                                       
InputProps={{
+                                                                               
...params.InputProps
+                                                                       }}
+                                                                       
placeholder="Select enum values"
+                                                               />
+                                                       )}
+                                               />
+                                       </>
+                               );
+                       }}
+               />
+       );
+};
+
+export default FormEnumMultiSelect;
diff --git 
a/dashboard/src/components/Forms/__tests__/FormEnumMultiSelect.test.tsx 
b/dashboard/src/components/Forms/__tests__/FormEnumMultiSelect.test.tsx
new file mode 100644
index 000000000..a4cf50579
--- /dev/null
+++ b/dashboard/src/components/Forms/__tests__/FormEnumMultiSelect.test.tsx
@@ -0,0 +1,103 @@
+/*
+ * 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 React from "react";
+import { render, screen } from "@utils/test-utils";
+import userEvent from "@testing-library/user-event";
+import { useForm } from "react-hook-form";
+import FormEnumMultiSelect from "../FormEnumMultiSelect";
+
+const optionsList = [
+       { label: "LRS", value: "LRS" },
+       { label: "ZRS", value: "ZRS" },
+       { label: "GRS", value: "GRS" }
+];
+
+const TestForm = ({
+       defaultValue = []
+}: {
+       defaultValue?: unknown;
+}) => {
+       const { control } = useForm({
+               defaultValues: {
+                       replicationTypes: defaultValue
+               }
+       });
+
+       return (
+               <FormEnumMultiSelect
+                       data={{
+                               name: "replicationTypes",
+                               isOptional: false,
+                               typeName: "array<adls_gen2_replication>",
+                               cardinality: "SET"
+                       }}
+                       control={control}
+                       optionsList={optionsList}
+               />
+       );
+};
+
+describe("FormEnumMultiSelect", () => {
+       it("renders a multi-select dropdown for array enum attributes", async 
() => {
+               const user = userEvent.setup();
+               render(<TestForm defaultValue={["LRS"]} />);
+
+               
expect(screen.getByText("ReplicationTypes")).toBeInTheDocument();
+               expect(screen.getByText("LRS")).toBeInTheDocument();
+
+               const valueInput = screen.getByRole("combobox");
+               await user.click(valueInput);
+               expect(await screen.findByRole("option", { name: "ZRS" 
})).toBeInTheDocument();
+               expect(screen.getByRole("option", { name: "GRS" 
})).toBeInTheDocument();
+       });
+
+       it("renders empty selection when no default value is provided", () => {
+               render(<TestForm />);
+
+               expect(screen.getByPlaceholderText("Select enum 
values")).toBeInTheDocument();
+       });
+
+       it("allows optional multi enum to remain empty (negative validation 
path)", () => {
+               const OptionalForm = () => {
+                       const { control } = useForm({
+                               defaultValues: {
+                                       storageTypes: []
+                               }
+                       });
+
+                       return (
+                               <FormEnumMultiSelect
+                                       data={{
+                                               name: "storageTypes",
+                                               isOptional: true,
+                                               typeName: 
"array<ozone_storage_type>",
+                                               cardinality: "SET"
+                                       }}
+                                       control={control}
+                                       optionsList={[
+                                               { label: "SSD", value: "SSD" },
+                                               { label: "DISK", value: "DISK" }
+                                       ]}
+                               />
+                       );
+               };
+
+               render(<OptionalForm />);
+               expect(screen.getByPlaceholderText("Select enum 
values")).toBeInTheDocument();
+       });
+});
diff --git a/dashboard/src/utils/__tests__/entityTypeConfigUtils.test.ts 
b/dashboard/src/utils/__tests__/entityTypeConfigUtils.test.ts
new file mode 100644
index 000000000..b0e6ad05c
--- /dev/null
+++ b/dashboard/src/utils/__tests__/entityTypeConfigUtils.test.ts
@@ -0,0 +1,100 @@
+/*
+ * 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 {
+       filterEditableEntityTypes,
+       isEntityTypeEditable,
+       parseEditableEntityTypes
+} from "@utils/entityTypeConfigUtils";
+
+describe("entityTypeConfigUtils", () => {
+       describe("parseEditableEntityTypes", () => {
+               it("returns wildcard for * config (positive)", () => {
+                       expect(parseEditableEntityTypes("*")).toBe("*");
+               });
+
+               it("parses comma-separated entity types with spaces 
(positive)", () => {
+                       expect(parseEditableEntityTypes("hdfs_path, 
enumchecking")).toEqual([
+                               "hdfs_path",
+                               "enumchecking"
+                       ]);
+               });
+
+               it("returns empty array for empty, null, or undefined config 
(negative)", () => {
+                       expect(parseEditableEntityTypes("")).toEqual([]);
+                       expect(parseEditableEntityTypes(null)).toEqual([]);
+                       expect(parseEditableEntityTypes(undefined)).toEqual([]);
+               });
+
+               it("returns empty array for whitespace-only config (negative)", 
() => {
+                       expect(parseEditableEntityTypes("   ")).toEqual([]);
+               });
+       });
+
+       describe("isEntityTypeEditable", () => {
+               it("allows any type when config is wildcard (positive)", () => {
+                       expect(isEntityTypeEditable("enumchecking", 
"*")).toBe(true);
+                       expect(isEntityTypeEditable("hdfs_path", 
"*")).toBe(true);
+               });
+
+               it("allows type when listed in comma-separated config 
(positive)", () => {
+                       expect(isEntityTypeEditable("enumchecking", 
"hdfs_path,enumchecking")).toBe(
+                               true
+                       );
+               });
+
+               it("denies type when not listed in config (negative)", () => {
+                       expect(isEntityTypeEditable("enumchecking", 
"hdfs_path")).toBe(false);
+                       expect(isEntityTypeEditable("DataSet", 
"hdfs_path,enumchecking")).toBe(
+                               false
+                       );
+               });
+
+               it("denies all types when config is empty (negative)", () => {
+                       expect(isEntityTypeEditable("enumchecking", 
"")).toBe(false);
+                       expect(isEntityTypeEditable("enumchecking", 
null)).toBe(false);
+               });
+       });
+
+       describe("filterEditableEntityTypes", () => {
+               const allTypes = ["hdfs_path", "enumchecking", "DataSet", 
"__internal"];
+
+               it("returns all types for wildcard config (positive)", () => {
+                       expect(filterEditableEntityTypes(allTypes, 
"*")).toEqual(allTypes);
+               });
+
+               it("returns only configured types for comma-separated config 
(positive)", () => {
+                       expect(
+                               filterEditableEntityTypes(allTypes, 
"hdfs_path,enumchecking")
+                       ).toEqual(["hdfs_path", "enumchecking"]);
+               });
+
+               it("returns empty list when config is empty (negative)", () => {
+                       expect(filterEditableEntityTypes(allTypes, 
"")).toEqual([]);
+               });
+
+               it("returns empty list when no types match config (negative)", 
() => {
+                       expect(filterEditableEntityTypes(allTypes, 
"unknown_type")).toEqual([]);
+               });
+
+               it("returns empty list when input type list is empty 
(negative)", () => {
+                       expect(filterEditableEntityTypes([], 
"hdfs_path,enumchecking")).toEqual(
+                               []
+                       );
+               });
+       });
+});
diff --git a/dashboard/src/utils/__tests__/enumTypeUtils.test.ts 
b/dashboard/src/utils/__tests__/enumTypeUtils.test.ts
new file mode 100644
index 000000000..3485ba615
--- /dev/null
+++ b/dashboard/src/utils/__tests__/enumTypeUtils.test.ts
@@ -0,0 +1,158 @@
+/*
+ * 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 {
+       areEnumOptionsEqual,
+       buildEnumOptionsForTypeName,
+       getInnerTypeName,
+       isArrayTypeName,
+       isEnumTypeName,
+       normalizeMultiEnumValue,
+       serializeMultiEnumValue
+} from "@utils/enumTypeUtils";
+
+const mockEnumDefs = [
+       {
+               name: "adls_gen2_replication",
+               elementDefs: [
+                       { value: "LRS" },
+                       { value: "ZRS" },
+                       { value: "GRS" },
+                       { value: "GZRS" },
+                       { value: "RA-GRS" }
+               ]
+       },
+       {
+               name: "ozone_storage_type",
+               elementDefs: [
+                       { value: "RAM_DISK" },
+                       { value: "SSD" },
+                       { value: "DISK" },
+                       { value: "ARCHIVE" }
+               ]
+       }
+];
+
+describe("enumTypeUtils", () => {
+       it("detects array enum type names", () => {
+               
expect(isArrayTypeName("array<adls_gen2_replication>")).toBe(true);
+               expect(isArrayTypeName("adls_gen2_replication")).toBe(false);
+       });
+
+       it("extracts inner enum type from array type name", () => {
+               expect(getInnerTypeName("array<adls_gen2_replication>")).toBe(
+                       "adls_gen2_replication"
+               );
+               expect(getInnerTypeName("adls_gen2_replication")).toBe(
+                       "adls_gen2_replication"
+               );
+       });
+
+       it("builds enum options for single and multi-value enum types", () => {
+               expect(
+                       buildEnumOptionsForTypeName("adls_gen2_replication", 
mockEnumDefs)
+               ).toEqual([
+                       { label: "LRS", value: "LRS" },
+                       { label: "ZRS", value: "ZRS" },
+                       { label: "GRS", value: "GRS" },
+                       { label: "GZRS", value: "GZRS" },
+                       { label: "RA-GRS", value: "RA-GRS" }
+               ]);
+
+               expect(
+                       
buildEnumOptionsForTypeName("array<adls_gen2_replication>", mockEnumDefs)
+               ).toEqual([
+                       { label: "LRS", value: "LRS" },
+                       { label: "ZRS", value: "ZRS" },
+                       { label: "GRS", value: "GRS" },
+                       { label: "GZRS", value: "GZRS" },
+                       { label: "RA-GRS", value: "RA-GRS" }
+               ]);
+       });
+
+       it("identifies enum and non-enum type names", () => {
+               expect(
+                       isEnumTypeName("array<adls_gen2_replication>", 
mockEnumDefs)
+               ).toBe(true);
+               expect(isEnumTypeName("array<string>", 
mockEnumDefs)).toBe(false);
+               expect(isEnumTypeName("string", mockEnumDefs)).toBe(false);
+               expect(isEnumTypeName("adls_gen2_replication", 
undefined)).toBe(false);
+       });
+
+       it("normalizes stored string values into enum option objects", () => {
+               const options = buildEnumOptionsForTypeName(
+                       "array<adls_gen2_replication>",
+                       mockEnumDefs
+               );
+
+               expect(normalizeMultiEnumValue(["LRS", "ZRS"], 
options)).toEqual([
+                       { label: "LRS", value: "LRS" },
+                       { label: "ZRS", value: "ZRS" }
+               ]);
+       });
+
+       it("returns empty array when multi enum value is missing or invalid", 
() => {
+               const options = buildEnumOptionsForTypeName(
+                       "array<adls_gen2_replication>",
+                       mockEnumDefs
+               );
+
+               expect(normalizeMultiEnumValue(undefined, options)).toEqual([]);
+               expect(normalizeMultiEnumValue("LRS", options)).toEqual([]);
+       });
+
+       it("serializes multi enum values for submit", () => {
+               expect(serializeMultiEnumValue(["LRS", "ZRS"])).toEqual(["LRS", 
"ZRS"]);
+               expect(
+                       serializeMultiEnumValue([
+                               { label: "LRS", value: "LRS" },
+                               { label: "ZRS", value: "ZRS" }
+                       ])
+               ).toEqual(["LRS", "ZRS"]);
+               expect(serializeMultiEnumValue(undefined)).toEqual([]);
+       });
+
+       it("compares enum options for autocomplete selection", () => {
+               const option = { label: "LRS", value: "LRS" };
+
+               expect(areEnumOptionsEqual(option, "LRS")).toBe(true);
+               expect(areEnumOptionsEqual(option, { label: "ZRS", value: "ZRS" 
})).toBe(
+                       false
+               );
+       });
+
+       it("builds ozone_storage_type enum options (positive)", () => {
+               expect(
+                       buildEnumOptionsForTypeName("ozone_storage_type", 
mockEnumDefs)
+               ).toEqual([
+                       { label: "RAM_DISK", value: "RAM_DISK" },
+                       { label: "SSD", value: "SSD" },
+                       { label: "DISK", value: "DISK" },
+                       { label: "ARCHIVE", value: "ARCHIVE" }
+               ]);
+       });
+
+       it("returns false for enum type when enumDefs is empty (negative)", () 
=> {
+               expect(isEnumTypeName("ozone_storage_type", [])).toBe(false);
+       });
+
+       it("returns empty options when enum type is not found (negative)", () 
=> {
+               expect(
+                       buildEnumOptionsForTypeName("unknown_enum_type", 
mockEnumDefs)
+               ).toEqual([]);
+       });
+});
diff --git a/dashboard/src/utils/entityTypeConfigUtils.ts 
b/dashboard/src/utils/entityTypeConfigUtils.ts
new file mode 100644
index 000000000..093844e66
--- /dev/null
+++ b/dashboard/src/utils/entityTypeConfigUtils.ts
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+export type EditableEntityTypesConfig = string[] | "*";
+
+export const parseEditableEntityTypes = (
+       editableTypesConfig: string | undefined | null
+): EditableEntityTypesConfig => {
+       if (!editableTypesConfig || editableTypesConfig.trim() === "") {
+               return [];
+       }
+
+       const trimmedConfig = editableTypesConfig.trim();
+       if (trimmedConfig === "*") {
+               return "*";
+       }
+
+       return trimmedConfig
+               .split(",")
+               .map((typeName) => typeName.trim())
+               .filter(Boolean);
+};
+
+export const isEntityTypeEditable = (
+       typeName: string,
+       editableTypesConfig: string | undefined | null
+): boolean => {
+       const parsedConfig = parseEditableEntityTypes(editableTypesConfig);
+
+       if (parsedConfig === "*") {
+               return true;
+       }
+
+       return parsedConfig.includes(typeName);
+};
+
+export const filterEditableEntityTypes = (
+       allTypeNames: string[],
+       editableTypesConfig: string | undefined | null
+): string[] => {
+       const parsedConfig = parseEditableEntityTypes(editableTypesConfig);
+
+       if (parsedConfig === "*") {
+               return allTypeNames;
+       }
+
+       return allTypeNames.filter((typeName) => 
parsedConfig.includes(typeName));
+};
diff --git a/dashboard/src/utils/enumTypeUtils.ts 
b/dashboard/src/utils/enumTypeUtils.ts
new file mode 100644
index 000000000..867707f14
--- /dev/null
+++ b/dashboard/src/utils/enumTypeUtils.ts
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+export interface EnumOption {
+       label: string;
+       value: string;
+}
+
+export interface EnumDef {
+       name: string;
+       elementDefs?: Array<{ value: string }>;
+}
+
+export const getInnerTypeName = (typeName: string): string => {
+       if (!typeName || typeName.indexOf("array<") !== 0) {
+               return typeName;
+       }
+
+       const match = typeName.match(/array<(.*)>/);
+       return match?.[1] || typeName;
+};
+
+export const isArrayTypeName = (typeName: string): boolean =>
+       Boolean(typeName && typeName.indexOf("array<") === 0);
+
+export const isEnumTypeName = (
+       typeName: string,
+       enumDefs: EnumDef[] | undefined
+): boolean => {
+       if (!typeName || !enumDefs?.length) {
+               return false;
+       }
+
+       const innerTypeName = getInnerTypeName(typeName);
+       return enumDefs.some((enumDef) => enumDef.name === innerTypeName);
+};
+
+export const buildEnumOptionsForTypeName = (
+       typeName: string,
+       enumDefs: EnumDef[] | undefined
+): EnumOption[] => {
+       if (typeName === "array<boolean>") {
+               return [
+                       { label: "true", value: "true" },
+                       { label: "false", value: "false" }
+               ];
+       }
+
+       if (!enumDefs?.length) {
+               return [];
+       }
+
+       const innerTypeName = getInnerTypeName(typeName);
+       const foundEnumType = enumDefs.find((enumDef) => enumDef.name === 
innerTypeName);
+       const elementDefs = foundEnumType?.elementDefs || [];
+
+       return elementDefs.map((elementDef) => ({
+               label: elementDef.value,
+               value: elementDef.value
+       }));
+};
+
+export const normalizeMultiEnumValue = (
+       value: unknown,
+       options: EnumOption[]
+): EnumOption[] => {
+       if (!Array.isArray(value)) {
+               return [];
+       }
+
+       return value.map((entry) => {
+               if (entry && typeof entry === "object" && "value" in entry) {
+                       const optionValue = String((entry as EnumOption).value);
+                       return (
+                               options.find((option) => option.value === 
optionValue) || {
+                                       label: optionValue,
+                                       value: optionValue
+                               }
+                       );
+               }
+
+               const optionValue = String(entry);
+               return (
+                       options.find((option) => option.value === optionValue) 
|| {
+                               label: optionValue,
+                               value: optionValue
+                       }
+               );
+       });
+};
+
+export const serializeMultiEnumValue = (value: unknown): string[] => {
+       if (!Array.isArray(value)) {
+               return [];
+       }
+
+       return value.map((entry) => {
+               if (entry && typeof entry === "object" && "value" in entry) {
+                       return String((entry as EnumOption).value);
+               }
+
+               return String(entry);
+       });
+};
+
+export const getEnumOptionLabel = (option: EnumOption | string): string => {
+       if (typeof option === "string") {
+               return option;
+       }
+
+       return option.label;
+};
+
+export const areEnumOptionsEqual = (
+       option: EnumOption,
+       value: EnumOption | string
+): boolean => {
+       if (typeof value === "string") {
+               return option.value === value;
+       }
+
+       return option.value === value.value;
+};
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/AttributeProperties.tsx 
b/dashboard/src/views/DetailPage/EntityDetailTabs/AttributeProperties.tsx
index bdf10246d..8efb029e7 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/AttributeProperties.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/AttributeProperties.tsx
@@ -24,6 +24,7 @@ import {
   LightTooltip
 } from "@components/muiComponents";
 import { isArray, isEmpty, isNull } from "@utils/Utils";
+import { isEntityTypeEditable } from "@utils/entityTypeConfigUtils";
 import SkeletonLoader from "@components/SkeletonLoader";
 import { getValues } from "@components/commonComponents";
 import { useState } from "react";
@@ -55,28 +56,13 @@ const AttributeProperties = ({
   const { entityDefs = [] } = entityData || {};
   const { data = {} } = sessionObj || {};
 
-  const key = "atlas.entity.update.allowed";
+  const updateAllowedKey = "atlas.entity.update.allowed";
+  const editableTypesKey = "atlas.ui.editable.entity.types";
 
-  let entityUpdate: boolean = false;
-  let entityTypeConfList: string[] = [];
-  
-  // Only process if the key exists and is not empty
-  if (!isEmpty(data?.[key])) {
-    let entityTypeList = data["atlas.ui.editable.entity.types"]
-      .trim()
-      .split(",");
-    if (entityTypeList.length) {
-      if (entityTypeList[0] === "*") {
-        entityTypeConfList = [];
-        entityUpdate = true; // Wildcard means all types are allowed
-      } else if (entityTypeList.length > 0) {
-        entityTypeConfList = entityTypeList;
-        // Check if current entity type is in the allowed list
-        if (entityTypeConfList.includes(typeName)) {
-          entityUpdate = true;
-        }
-      }
-    }
+  let entityUpdate = false;
+
+  if (!isEmpty(data?.[updateAllowedKey])) {
+    entityUpdate = isEntityTypeEditable(typeName, data?.[editableTypesKey]);
   }
 
   const [entityModal, setEntityModal] = useState<boolean>(false);
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/__tests__/AttributeProperties.test.tsx
 
b/dashboard/src/views/DetailPage/EntityDetailTabs/__tests__/AttributeProperties.test.tsx
index 158b9b66f..ba2b8fef8 100644
--- 
a/dashboard/src/views/DetailPage/EntityDetailTabs/__tests__/AttributeProperties.test.tsx
+++ 
b/dashboard/src/views/DetailPage/EntityDetailTabs/__tests__/AttributeProperties.test.tsx
@@ -617,6 +617,70 @@ describe('AttributeProperties', () => {
                        
expect(screen.queryByTestId('custom-button')).not.toBeInTheDocument();
                });
 
+               it('should show Edit button for enumchecking when type is 
allowed (positive)', () => {
+                       mockUseAppSelector.mockImplementation((selector: any) 
=> {
+                               const mockState = {
+                                       session: {
+                                               sessionObj: {
+                                                       data: {
+                                                               
'atlas.entity.update.allowed': true,
+                                                               
'atlas.ui.editable.entity.types': 'hdfs_path,enumchecking'
+                                                       }
+                                               }
+                                       }
+                               };
+                               return selector(mockState);
+                       });
+
+                       render(
+                               <TestWrapper>
+                                       <AttributeProperties
+                                               entity={{
+                                                       ...defaultMockEntity,
+                                                       typeName: 'enumchecking'
+                                               }}
+                                               
referredEntities={defaultMockReferredEntities}
+                                               loading={false}
+                                               propertiesName="Technical"
+                                       />
+                               </TestWrapper>
+                       );
+
+                       
expect(screen.getByTestId('custom-button')).toBeInTheDocument();
+               });
+
+               it('should hide Edit button for enumchecking when type is not 
allowed (negative)', () => {
+                       mockUseAppSelector.mockImplementation((selector: any) 
=> {
+                               const mockState = {
+                                       session: {
+                                               sessionObj: {
+                                                       data: {
+                                                               
'atlas.entity.update.allowed': true,
+                                                               
'atlas.ui.editable.entity.types': 'hdfs_path'
+                                                       }
+                                               }
+                                       }
+                               };
+                               return selector(mockState);
+                       });
+
+                       render(
+                               <TestWrapper>
+                                       <AttributeProperties
+                                               entity={{
+                                                       ...defaultMockEntity,
+                                                       typeName: 'enumchecking'
+                                               }}
+                                               
referredEntities={defaultMockReferredEntities}
+                                               loading={false}
+                                               propertiesName="Technical"
+                                       />
+                               </TestWrapper>
+                       );
+
+                       
expect(screen.queryByTestId('custom-button')).not.toBeInTheDocument();
+               });
+
                it('should not show Edit button when 
atlas.entity.update.allowed is empty', () => {
                        mockUseAppSelector.mockImplementation((selector: any) 
=> {
                                const mockState = {
diff --git a/dashboard/src/views/Entity/EntityForm.tsx 
b/dashboard/src/views/Entity/EntityForm.tsx
index d933b0ec1..c43d93825 100644
--- a/dashboard/src/views/Entity/EntityForm.tsx
+++ b/dashboard/src/views/Entity/EntityForm.tsx
@@ -21,16 +21,17 @@ import {
   getEntity,
   getTypedef
 } from "@api/apiMethods/entityFormApiMethod";
-import FormAutocomplete from "@components/Forms/FormAutocomplete";
-import FormCreatableSelect from "@components/Forms/FormCreatableSelect";
-import FormDatepicker from "@components/Forms/FormDatepicker";
-import FormInputText from "@components/Forms/FormInputText";
-import FormSelectBoolean from "@components/Forms/FormSelectBoolean";
-import FormTextArea from "@components/Forms/FormTextArea";
 import CustomModal from "@components/Modal";
 import SkeletonLoader from "@components/SkeletonLoader";
 import { useAppDispatch, useAppSelector } from "@hooks/reducerHook";
 import { Action, DynamicObject, State } from "@models/entityFormType";
+import {
+  buildEnumOptionsForTypeName,
+  isArrayTypeName,
+  isEnumTypeName,
+  normalizeMultiEnumValue,
+  serializeMultiEnumValue
+} from "@utils/enumTypeUtils";
 import {
   Autocomplete,
   FormControl,
@@ -51,11 +52,13 @@ import {
   isString,
   serverError
 } from "@utils/Utils";
+import { filterEditableEntityTypes } from "@utils/entityTypeConfigUtils";
 import moment from "moment";
 import { useEffect, useReducer, useRef, useState } from "react";
 import { useForm } from "react-hook-form";
 import { useNavigate, useParams, useSearchParams } from "react-router-dom";
 import { toast } from "react-toastify";
+import { renderEntityFormControl } from "./entityFormFields";
 
 export const initialState: State = {
   entityTypeObj: null,
@@ -97,10 +100,12 @@ const EntityForm = ({
     (state: any) => state.session
   );
   const { data = {} } = sessionObj || {};
-  const entityTypes = data?.[key] || {};
+  const editableEntityTypesConfig = data?.[key];
   const { typeHeaderData }: any = useAppSelector(
     (state: any) => state.typeHeader
   );
+  const { enumObj = {} }: any = useAppSelector((state: any) => state.enum);
+  const { enumDefs = [] } = enumObj?.data || {};
   const toastId: any = useRef(null);
   const [state, dispatch] = useReducer(reducer, initialState);
   const [typeValue, setTypeValue] = useState<any>("");
@@ -217,6 +222,15 @@ const EntityForm = ({
             )
           ) {
             val = extractValue(value, type);
+          } else if (isEnumTypeName(type, enumDefs)) {
+            if (isArrayTypeName(type)) {
+              val = normalizeMultiEnumValue(
+                value,
+                buildEnumOptionsForTypeName(type, enumDefs)
+              );
+            } else {
+              val = value || null;
+            }
           } else if (type === "date" || type === "time") {
             val = moment(value);
           } else if (
@@ -314,9 +328,12 @@ const EntityForm = ({
   const { entityDefs = {} } = entityData || {};
 
   const optionsList = !isEmpty(entityDefs)
-    ? entityDefs.map((entity: { name: string }) => {
-        return entity.name;
-      })
+    ? filterEditableEntityTypes(
+        entityDefs
+          .map((entity: { name: string }) => entity.name)
+          .filter((name: string) => name.indexOf("__") !== 0),
+        editableEntityTypesConfig
+      )
     : [];
 
   const { entityTypeObj }: State = state;
@@ -389,6 +406,12 @@ const EntityForm = ({
           )
         ) {
           val = extractValue(value, type);
+        } else if (isEnumTypeName(type, enumDefs)) {
+          if (isArrayTypeName(type)) {
+            val = !isEmpty(value) ? serializeMultiEnumValue(value) : null;
+          } else {
+            val = isString(value) && value.length ? value : null;
+          }
         } else if (type === "date" || type === "time") {
           val = value?.valueOf();
         } else if (
@@ -472,36 +495,13 @@ const EntityForm = ({
   };
 
   const renderFormControl: any = (obj: any) => {
-    let attributeObj = entityDefs.find(
-      (typedef: any) => typedef.name == obj.typeName
-    );
-    let typeHeaderObj = typeHeaderData.find(
-      (typeheader: { name: string }) => typeheader.name == obj.typeName
-    );
-    switch (obj.typeName) {
-      case (obj.typeName.indexOf("map") > -1 ||
-        typeHeaderObj?.category === "STRUCT") &&
-        obj.typeName:
-        return <FormTextArea data={obj} control={control} />;
-      case (attributeObj && obj.typeName) ||
-        (obj.typeName.indexOf("array") > -1 &&
-          obj.cardinality == "SET" &&
-          obj.typeName):
-        return <FormAutocomplete data={obj} control={control} />;
-      case (attributeObj && obj.typeName) ||
-        (obj.typeName.indexOf("array") > -1 &&
-          obj.cardinality == "SINGLE" &&
-          obj.typeName):
-        return <FormCreatableSelect data={obj} control={control} />;
-      case "boolean":
-        return <FormSelectBoolean data={obj} control={control} />;
-      case "date":
-      case "time":
-        return <FormDatepicker data={obj} control={control} />;
-
-      default:
-        return <FormInputText data={obj} control={control} />;
-    }
+    return renderEntityFormControl({
+      obj,
+      control,
+      entityDefs,
+      typeHeaderData,
+      enumDefs
+    });
   };
   const { name } = extractKeyValueFromEntity(entity) || { name: '', found: 
false, key: '' };
 
@@ -550,18 +550,7 @@ const EntityForm = ({
                 }}
                 disableClearable={true}
                 id="search-by-type"
-                options={optionsList
-                  .map((obj: any) => {
-                    if (entityTypes != "*") {
-                      if (obj == entityTypes) {
-                        return obj;
-                      }
-                    } else {
-                      return obj;
-                    }
-                  })
-                  .filter(Boolean)
-                  .sort()}
+                options={optionsList.sort()}
                 className="entity-form-type-select"
                 renderInput={(params) => {
                   return (
diff --git a/dashboard/src/views/Entity/entityFormFields.tsx 
b/dashboard/src/views/Entity/entityFormFields.tsx
new file mode 100644
index 000000000..9ad99ff52
--- /dev/null
+++ b/dashboard/src/views/Entity/entityFormFields.tsx
@@ -0,0 +1,108 @@
+/*
+ * 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 FormAutocomplete from "@components/Forms/FormAutocomplete";
+import FormCreatableSelect from "@components/Forms/FormCreatableSelect";
+import FormDatepicker from "@components/Forms/FormDatepicker";
+import FormEnumMultiSelect from "@components/Forms/FormEnumMultiSelect";
+import FormInputText from "@components/Forms/FormInputText";
+import FormSelectBoolean from "@components/Forms/FormSelectBoolean";
+import FormSingleSelect from "@components/Forms/FormSingleSelect";
+import FormTextArea from "@components/Forms/FormTextArea";
+import {
+       buildEnumOptionsForTypeName,
+       EnumDef,
+       isArrayTypeName,
+       isEnumTypeName
+} from "@utils/enumTypeUtils";
+
+export const renderEntityFormControl = ({
+       obj,
+       control,
+       entityDefs,
+       typeHeaderData,
+       enumDefs
+}: {
+       obj: {
+               name: string;
+               typeName: string;
+               isOptional: boolean;
+               cardinality?: string;
+       };
+       control: any;
+       entityDefs: Array<{ name: string }>;
+       typeHeaderData: Array<{ name: string; category?: string }>;
+       enumDefs: EnumDef[];
+}) => {
+       const attributeObj = entityDefs.find(
+               (typedef) => typedef.name === obj.typeName
+       );
+       const typeHeaderObj = typeHeaderData.find(
+               (typeheader) => typeheader.name === obj.typeName
+       );
+       const enumOptions = buildEnumOptionsForTypeName(obj.typeName, enumDefs);
+       const isEnumType = isEnumTypeName(obj.typeName, enumDefs);
+       const isMultiEnumType = isArrayTypeName(obj.typeName) && isEnumType;
+       const isEntityReference = Boolean(attributeObj);
+       const isArrayType = obj.typeName.indexOf("array") > -1;
+
+       if (
+               obj.typeName.indexOf("map") > -1 ||
+               typeHeaderObj?.category === "STRUCT"
+       ) {
+               return <FormTextArea data={obj} control={control} />;
+       }
+
+       if (isMultiEnumType) {
+               return (
+                       <FormEnumMultiSelect
+                               data={obj}
+                               control={control}
+                               optionsList={enumOptions}
+                       />
+               );
+       }
+
+       if (isEnumType) {
+               return (
+                       <FormSingleSelect
+                               data={obj}
+                               control={control}
+                               optionsList={enumOptions}
+                               typeName={obj.typeName}
+                       />
+               );
+       }
+
+       if (isEntityReference || (isArrayType && obj.cardinality === "SET")) {
+               return <FormAutocomplete data={obj} control={control} />;
+       }
+
+       if (isEntityReference || (isArrayType && obj.cardinality === "SINGLE")) 
{
+               return <FormCreatableSelect data={obj} control={control} />;
+       }
+
+       if (obj.typeName === "boolean") {
+               return <FormSelectBoolean data={obj} control={control} />;
+       }
+
+       if (obj.typeName === "date" || obj.typeName === "time") {
+               return <FormDatepicker data={obj} control={control} />;
+       }
+
+       return <FormInputText data={obj} control={control} />;
+};
diff --git a/dashboard/src/views/__tests__/EntityForm.test.tsx 
b/dashboard/src/views/__tests__/EntityForm.test.tsx
index 63d336faf..d69f46862 100644
--- a/dashboard/src/views/__tests__/EntityForm.test.tsx
+++ b/dashboard/src/views/__tests__/EntityForm.test.tsx
@@ -136,6 +136,16 @@ jest.mock('@components/Forms/FormCreatableSelect', () => ({
        default: ({ data }: any) => <input 
data-testid={`creatable-${data.name}`} />
 }));
 
+jest.mock('@components/Forms/FormSingleSelect', () => ({
+       __esModule: true,
+       default: ({ data }: any) => <select 
data-testid={`enum-single-${data.name}`} />
+}));
+
+jest.mock('@components/Forms/FormEnumMultiSelect', () => ({
+       __esModule: true,
+       default: ({ data }: any) => <select multiple 
data-testid={`enum-multi-${data.name}`} />
+}));
+
 jest.mock('@components/Modal', () => ({
        __esModule: true,
        default: ({ open, onClose, children, title, button1Handler, 
button2Handler, button2Label }: any) =>
@@ -154,12 +164,18 @@ jest.mock('@components/SkeletonLoader', () => ({
        default: () => <div data-testid="skeleton">Loading...</div>
 }));
 
-const createStore = (entityData: any = {}, sessionData: any = {}, 
typeHeaderData: any = []) => {
+const createStore = (
+       entityData: any = {},
+       sessionData: any = {},
+       typeHeaderData: any = [],
+       enumData: any = { enumDefs: [] }
+) => {
        return configureStore({
                reducer: {
                        entity: () => ({ entityData }),
                        session: () => ({ sessionObj: { data: sessionData } }),
-                       typeHeader: () => ({ typeHeaderData })
+                       typeHeader: () => ({ typeHeaderData }),
+                       enum: () => ({ enumObj: { data: enumData } })
                },
                middleware: (getDefaultMiddleware) =>
                        getDefaultMiddleware({
@@ -204,6 +220,30 @@ describe('EntityForm - 100% Coverage', () => {
                                        { name: 'output', typeName: 'DataSet', 
isOptional: true, cardinality: 'SINGLE' },
                                        { name: 'outputs', typeName: 
'array<DataSet>', isOptional: true, cardinality: 'SINGLE' }
                                ]
+                       },
+                       {
+                               name: 'enumchecking',
+                               category: 'ENTITY',
+                               attributeDefs: [
+                                       { name: 'name', typeName: 'string', 
isOptional: false, cardinality: 'SINGLE' },
+                                       { name: 'storageType', typeName: 
'ozone_storage_type', isOptional: true, cardinality: 'SINGLE' },
+                                       { name: 'requiredStorageType', 
typeName: 'ozone_storage_type', isOptional: false, cardinality: 'SINGLE' },
+                                       { name: 'storageTypes', typeName: 
'array<ozone_storage_type>', isOptional: true, cardinality: 'SET' }
+                               ]
+                       },
+                       {
+                               name: 'hdfs_path',
+                               category: 'ENTITY',
+                               attributeDefs: [
+                                       { name: 'path', typeName: 'string', 
isOptional: false, cardinality: 'SINGLE' }
+                               ]
+                       },
+                       {
+                               name: '__internal_type',
+                               category: 'ENTITY',
+                               attributeDefs: [
+                                       { name: 'internalAttr', typeName: 
'string', isOptional: false, cardinality: 'SINGLE' }
+                               ]
                        }
                ]
        };
@@ -1750,6 +1790,58 @@ describe('EntityForm - 100% Coverage', () => {
        });
 
        describe('Entity Type Selection', () => {
+               const openEntityTypeDropdown = async () => {
+                       const autocomplete = 
screen.getByLabelText('Search-entity-type');
+                       fireEvent.mouseDown(autocomplete);
+                       return autocomplete;
+               };
+
+               test('shows enumchecking when listed in session config 
(positive)', async () => {
+                       const restrictedSessionData = {
+                               'atlas.ui.editable.entity.types': 
'hdfs_path,enumchecking'
+                       };
+                       const store = createStore(mockEntityData, 
restrictedSessionData, mockTypeHeaderData);
+
+                       render(
+                               <Provider store={store}>
+                                       <MemoryRouter>
+                                               <EntityForm open={true} 
onClose={jest.fn()} />
+                                       </MemoryRouter>
+                               </Provider>
+                       );
+
+                       await openEntityTypeDropdown();
+
+                       await waitFor(() => {
+                               expect(screen.getByRole('option', { name: 
'enumchecking' })).toBeInTheDocument();
+                               expect(screen.getByRole('option', { name: 
'hdfs_path' })).toBeInTheDocument();
+                       });
+                       expect(screen.queryByRole('option', { name: 'DataSet' 
})).not.toBeInTheDocument();
+                       expect(screen.queryByRole('option', { name: 
'__internal_type' })).not.toBeInTheDocument();
+               });
+
+               test('hides enumchecking when not listed in session config 
(negative)', async () => {
+                       const restrictedSessionData = {
+                               'atlas.ui.editable.entity.types': 'DataSet'
+                       };
+                       const store = createStore(mockEntityData, 
restrictedSessionData, mockTypeHeaderData);
+
+                       render(
+                               <Provider store={store}>
+                                       <MemoryRouter>
+                                               <EntityForm open={true} 
onClose={jest.fn()} />
+                                       </MemoryRouter>
+                               </Provider>
+                       );
+
+                       await openEntityTypeDropdown();
+
+                       await waitFor(() => {
+                               expect(screen.getByRole('option', { name: 
'DataSet' })).toBeInTheDocument();
+                       });
+                       expect(screen.queryByRole('option', { name: 
'enumchecking' })).not.toBeInTheDocument();
+               });
+
                test('filters entity types when session config is not 
wildcard', () => {
                        const restrictedSessionData = {
                                'atlas.ui.editable.entity.types': 'DataSet'
@@ -1767,7 +1859,7 @@ describe('EntityForm - 100% Coverage', () => {
                        expect(screen.getByTestId('modal')).toBeInTheDocument();
                });
 
-               test('shows all entity types when session config is wildcard', 
() => {
+               test('shows all entity types when session config is wildcard', 
async () => {
                        const store = createStore(mockEntityData, 
mockSessionData, mockTypeHeaderData);
                        
                        render(
@@ -1778,7 +1870,12 @@ describe('EntityForm - 100% Coverage', () => {
                                </Provider>
                        );
 
-                       expect(screen.getByTestId('modal')).toBeInTheDocument();
+                       await openEntityTypeDropdown();
+
+                       await waitFor(() => {
+                               expect(screen.getByRole('option', { name: 
'enumchecking' })).toBeInTheDocument();
+                               expect(screen.getByRole('option', { name: 
'DataSet' })).toBeInTheDocument();
+                       });
                });
 
                test('renders entity type selection autocomplete', async () => {
diff --git a/dashboard/src/views/__tests__/EntityFormEnum.test.tsx 
b/dashboard/src/views/__tests__/EntityFormEnum.test.tsx
new file mode 100644
index 000000000..a08662116
--- /dev/null
+++ b/dashboard/src/views/__tests__/EntityFormEnum.test.tsx
@@ -0,0 +1,285 @@
+/*
+ * 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 React from "react";
+import { render, screen } from "@testing-library/react";
+import { useForm } from "react-hook-form";
+import { renderEntityFormControl } from "../Entity/entityFormFields";
+
+jest.mock("@components/Forms/FormInputText", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <input data-testid={`input-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormSingleSelect", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <select data-testid={`enum-single-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormEnumMultiSelect", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <select multiple data-testid={`enum-multi-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormAutocomplete", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <input data-testid={`autocomplete-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormCreatableSelect", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <input data-testid={`creatable-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormSelectBoolean", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <select data-testid={`boolean-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormDatepicker", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <input data-testid={`date-${data.name}`} />
+       )
+}));
+
+jest.mock("@components/Forms/FormTextArea", () => ({
+       __esModule: true,
+       default: ({ data }: { data: { name: string } }) => (
+               <textarea data-testid={`textarea-${data.name}`} />
+       )
+}));
+
+const mockEnumDefs = [
+       {
+               name: "adls_gen2_replication",
+               elementDefs: [{ value: "LRS" }, { value: "ZRS" }, { value: 
"GRS" }]
+       },
+       {
+               name: "ozone_storage_type",
+               elementDefs: [
+                       { value: "RAM_DISK" },
+                       { value: "SSD" },
+                       { value: "DISK" },
+                       { value: "ARCHIVE" }
+               ]
+       }
+];
+
+const TestHarness = ({
+       typeName,
+       name,
+       cardinality = "SINGLE"
+}: {
+       typeName: string;
+       name: string;
+       cardinality?: string;
+}) => {
+       const { control } = useForm();
+
+       return renderEntityFormControl({
+               obj: {
+                       name,
+                       typeName,
+                       isOptional: true,
+                       cardinality
+               },
+               control,
+               entityDefs: [{ name: "DataSet" }],
+               typeHeaderData: [{ name: "CustomStruct", category: "STRUCT" }],
+               enumDefs: mockEnumDefs
+       });
+};
+
+describe("entityFormFields enum rendering", () => {
+       it("renders single enum attributes as dropdown select", () => {
+               render(
+                       <TestHarness
+                               name="replication"
+                               typeName="adls_gen2_replication"
+                       />
+               );
+
+               
expect(screen.getByTestId("enum-single-replication")).toBeInTheDocument();
+       });
+
+       it("renders array enum attributes as multi-select dropdown", () => {
+               render(
+                       <TestHarness
+                               name="replicationTypes"
+                               typeName="array<adls_gen2_replication>"
+                               cardinality="SET"
+                       />
+               );
+
+               
expect(screen.getByTestId("enum-multi-replicationTypes")).toBeInTheDocument();
+       });
+
+       it("falls back to text input for unknown enum typedef names", () => {
+               render(
+                       <TestHarness name="unknownEnum" 
typeName="unknown_enum_type" />
+               );
+
+               
expect(screen.getByTestId("input-unknownEnum")).toBeInTheDocument();
+               expect(
+                       screen.queryByTestId("enum-single-unknownEnum")
+               ).not.toBeInTheDocument();
+       });
+
+       it("keeps primitive string attributes on text input", () => {
+               render(<TestHarness name="description" typeName="string" />);
+
+               
expect(screen.getByTestId("input-description")).toBeInTheDocument();
+               
expect(screen.queryByTestId("enum-single-description")).not.toBeInTheDocument();
+       });
+
+       it("keeps entity reference arrays on autocomplete instead of enum 
dropdown", () => {
+               render(
+                       <TestHarness
+                               name="inputs"
+                               typeName="array<DataSet>"
+                               cardinality="SET"
+                       />
+               );
+
+               
expect(screen.getByTestId("autocomplete-inputs")).toBeInTheDocument();
+               
expect(screen.queryByTestId("enum-multi-inputs")).not.toBeInTheDocument();
+       });
+
+       it("renders ozone_storage_type single enum as dropdown (positive)", () 
=> {
+               render(
+                       <TestHarness name="storageType" 
typeName="ozone_storage_type" />
+               );
+
+               
expect(screen.getByTestId("enum-single-storageType")).toBeInTheDocument();
+               
expect(screen.queryByTestId("input-storageType")).not.toBeInTheDocument();
+       });
+
+       it("renders array<ozone_storage_type> as multi-select (positive)", () 
=> {
+               render(
+                       <TestHarness
+                               name="storageTypes"
+                               typeName="array<ozone_storage_type>"
+                               cardinality="SET"
+                       />
+               );
+
+               
expect(screen.getByTestId("enum-multi-storageTypes")).toBeInTheDocument();
+       });
+
+       it("renders map attributes as textarea instead of enum dropdown 
(negative)", () => {
+               render(
+                       <TestHarness name="metadata" 
typeName="map<string,string>" />
+               );
+
+               
expect(screen.getByTestId("textarea-metadata")).toBeInTheDocument();
+               expect(
+                       screen.queryByTestId("enum-single-metadata")
+               ).not.toBeInTheDocument();
+       });
+
+       it("renders boolean attributes as boolean select instead of enum 
dropdown (negative)", () => {
+               render(<TestHarness name="isActive" typeName="boolean" />);
+
+               
expect(screen.getByTestId("boolean-isActive")).toBeInTheDocument();
+               
expect(screen.queryByTestId("enum-single-isActive")).not.toBeInTheDocument();
+       });
+
+       it("falls back to text input when enumDefs list is empty (negative)", 
() => {
+               const EmptyEnumHarness = () => {
+                       const { control } = useForm();
+
+                       return renderEntityFormControl({
+                               obj: {
+                                       name: "storageType",
+                                       typeName: "ozone_storage_type",
+                                       isOptional: true
+                               },
+                               control,
+                               entityDefs: [{ name: "DataSet" }],
+                               typeHeaderData: [],
+                               enumDefs: []
+                       });
+               };
+
+               render(<EmptyEnumHarness />);
+
+               
expect(screen.getByTestId("input-storageType")).toBeInTheDocument();
+               expect(
+                       screen.queryByTestId("enum-single-storageType")
+               ).not.toBeInTheDocument();
+       });
+
+       it("renders date attributes with datepicker (hdfs_path-style 
primitive)", () => {
+               render(<TestHarness name="modifiedTime" typeName="date" />);
+
+               
expect(screen.getByTestId("date-modifiedTime")).toBeInTheDocument();
+               expect(
+                       screen.queryByTestId("enum-single-modifiedTime")
+               ).not.toBeInTheDocument();
+       });
+
+       it("renders struct attributes as textarea instead of enum dropdown", () 
=> {
+               const StructHarness = () => {
+                       const { control } = useForm();
+
+                       return renderEntityFormControl({
+                               obj: {
+                                       name: "customStruct",
+                                       typeName: "CustomStruct",
+                                       isOptional: true
+                               },
+                               control,
+                               entityDefs: [],
+                               typeHeaderData: [{ name: "CustomStruct", 
category: "STRUCT" }],
+                               enumDefs: mockEnumDefs
+                       });
+               };
+
+               render(<StructHarness />);
+
+               
expect(screen.getByTestId("textarea-customStruct")).toBeInTheDocument();
+               expect(
+                       screen.queryByTestId("enum-single-customStruct")
+               ).not.toBeInTheDocument();
+       });
+
+       it("renders entity reference arrays with creatable select for SINGLE 
cardinality", () => {
+               render(
+                       <TestHarness
+                               name="path"
+                               typeName="array<hdfs_path>"
+                               cardinality="SINGLE"
+                       />
+               );
+
+               
expect(screen.getByTestId("creatable-path")).toBeInTheDocument();
+               
expect(screen.queryByTestId("enum-multi-path")).not.toBeInTheDocument();
+       });
+});
diff --git a/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js 
b/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js
index be413bfa6..dbbc9f78f 100644
--- a/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js
+++ b/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js
@@ -73,7 +73,7 @@ define(['require',
              * @constructs
              */
             initialize: function(options) {
-                _.extend(this, _.pick(options, 'guid', 'callback', 
'showLoader', 'entityDefCollection', 'typeHeaders', 'searchVent'));
+                _.extend(this, _.pick(options, 'guid', 'callback', 
'showLoader', 'entityDefCollection', 'typeHeaders', 'searchVent', 
'enumDefCollection'));
                 var that = this,
                     entityTitle, okLabel;
                 this.selectStoreCollection = new Backbone.Collection();
@@ -473,6 +473,25 @@ define(['require',
                         this.value = setValue;
                     }
                 });
+                this.$('select[data-enum="true"]').each(function() {
+                    var $this = $(this),
+                        dataKey = $this.data('key');
+                    if ($this.prop('multiple')) {
+                        $this.select2({
+                            allowClear: !$this.hasClass('true'),
+                            multiple: true,
+                            placeholder: '-- Select --'
+                        });
+                    }
+                    if (that.guid && that.entityData) {
+                        var setValue = 
that.entityData.get("entity").attributes[dataKey];
+                        if ($this.prop('multiple') && _.isArray(setValue)) {
+                            $this.val(setValue).trigger('change');
+                        } else if (!$this.prop('multiple') && setValue) {
+                            $this.val(setValue).trigger('change');
+                        }
+                    }
+                });
                 this.addJsonSearchData();
             },
             initializeValidation: function() {
@@ -526,6 +545,60 @@ define(['require',
                     typeOfDefination = (relationshipType ? "Relationships" : 
"Attributes");
                 return '<div class="attribute-dash-box ' + (alloptional ? 
"alloptional" : "") + ' "><span class="attribute-type-label">' + 
(typeOfDefination) + '</span>' + htmlField + '</div>';
             },
+            getInnerEnumTypeName: function(typeName) {
+                if (!typeName || typeName.indexOf("array<") !== 0) {
+                    return typeName;
+                }
+                var match = typeName.match(/array<(.*)>/);
+                return match && match[1] ? match[1] : typeName;
+            },
+            getEnumTypeDef: function(typeName) {
+                if (!this.enumDefCollection || !typeName) {
+                    return null;
+                }
+                var innerEnumTypeName = this.getInnerEnumTypeName(typeName);
+                return this.enumDefCollection.fullCollection.findWhere({ name: 
innerEnumTypeName });
+            },
+            getEnumSelect: function(object) {
+                var value = object.value,
+                    name = _.escape(value.name),
+                    entityValue = object.entityValue,
+                    isAttribute = object.isAttribute,
+                    isRelation = object.isRelation,
+                    enumTypeDef = object.enumTypeDef,
+                    isMultiValued = object.isMultiValued,
+                    typeName = value.typeName,
+                    optionsHtml = "",
+                    selectedValues = [];
+
+                if (isMultiValued && _.isArray(entityValue)) {
+                    selectedValues = entityValue;
+                }
+
+                if (!isMultiValued && value.isOptional) {
+                    optionsHtml += '<option value="">-- Select ' + 
_.escape(enumTypeDef.get("name")) + ' --</option>';
+                }
+
+                _.each(enumTypeDef.get("elementDefs"), function(elementDef) {
+                    var selected = "";
+                    if (isMultiValued) {
+                        if (selectedValues.indexOf(elementDef.value) > -1) {
+                            selected = " selected";
+                        }
+                    } else if (entityValue && elementDef.value === 
entityValue) {
+                        selected = " selected";
+                    }
+                    optionsHtml += '<option value="' + 
_.escape(elementDef.value) + '"' + selected + '>' + _.escape(elementDef.value) 
+ '</option>';
+                });
+
+                return '<select class="form-control row-margin-bottom 
entityInputBox ' + (value.isOptional === true ? "false" : "true") +
+                    '" data-type="' + typeName +
+                    '" data-attribute="' + isAttribute +
+                    '" data-relation="' + isRelation +
+                    '" data-key="' + name +
+                    '" data-enum="true"' +
+                    ' data-id="entityInput"' + (isMultiValued ? ' 
multiple="multiple"' : '') + '>' + optionsHtml + '</select>';
+            },
             getSelect: function(object) {
                 var value = object.value,
                     name = _.escape(value.name),
@@ -606,7 +679,9 @@ define(['require',
                     entityValue = "";
                 if (this.guid) {
                     var dataValue = 
this.entityData.get("entity").attributes[value.name];
-                    if (_.isObject(dataValue)) {
+                    if (_.isArray(dataValue)) {
+                        entityValue = dataValue;
+                    } else if (_.isObject(dataValue)) {
                         entityValue = JSON.stringify(dataValue);
                     } else {
                         if (dataValue) {
@@ -621,6 +696,17 @@ define(['require',
                         }
                     }
                 }
+                var enumTypeDef = this.getEnumTypeDef(typeName);
+                if (enumTypeDef) {
+                    return this.getEnumSelect({
+                        "value": value,
+                        "entityValue": entityValue,
+                        "isAttribute": isAttribute,
+                        "isRelation": isRelation,
+                        "enumTypeDef": enumTypeDef,
+                        "isMultiValued": typeName.indexOf("array<") === 0
+                    });
+                }
                 if ((typeName && 
this.entityDefCollection.fullCollection.find({ name: typeName })) || typeName 
=== "boolean" || typeName.indexOf("array") > -1) {
                     return this.getSelect({
                         "value": value,
@@ -721,7 +807,13 @@ define(['require',
                             val = null;
                         // Extract Data
                         if (dataTypeEnitity && datakeyEntity) {
-                            if (that.entityDefCollection.fullCollection.find({ 
name: dataTypeEnitity })) {
+                            if ($(this).data('enum')) {
+                                if ($(this).prop('multiple')) {
+                                    val = (value && value.length) ? value : 
null;
+                                } else {
+                                    val = (value && value.length) ? value : 
null;
+                                }
+                            } else if 
(that.entityDefCollection.fullCollection.find({ name: dataTypeEnitity })) {
                                 val = extractValue(value, typeName);
                             } else if (dataTypeEnitity === 'date' || 
dataTypeEnitity === 'time') {
                                 val = Date.parse(value);
diff --git a/dashboardv2/public/js/views/entity/EntityDetailTableLayoutView.js 
b/dashboardv2/public/js/views/entity/EntityDetailTableLayoutView.js
index eb4647c2c..d162fb97f 100644
--- a/dashboardv2/public/js/views/entity/EntityDetailTableLayoutView.js
+++ b/dashboardv2/public/js/views/entity/EntityDetailTableLayoutView.js
@@ -67,7 +67,7 @@ define(['require',
              * @constructs
              */
             initialize: function(options) {
-                _.extend(this, _.pick(options, 'entity', 'typeHeaders', 
'attributeDefs', 'attributes', 'editEntity', 'guid', 'entityDefCollection', 
'searchVent', 'fetchCollection', 'isRelationshipDetailPage'));
+                _.extend(this, _.pick(options, 'entity', 'typeHeaders', 
'attributeDefs', 'attributes', 'editEntity', 'guid', 'entityDefCollection', 
'enumDefCollection', 'searchVent', 'fetchCollection', 
'isRelationshipDetailPage'));
                 this.entityModel = new VEntity({});
                 this.showAllProperties = false;
             },
@@ -104,6 +104,7 @@ define(['require',
                         searchVent: that.searchVent,
                         entityDefCollection: that.entityDefCollection,
                         typeHeaders: that.typeHeaders,
+                        enumDefCollection: that.enumDefCollection,
                         callback: function() {
                             that.fetchCollection();
                         }
diff --git a/dashboardv2/public/js/views/search/SearchResultLayoutView.js 
b/dashboardv2/public/js/views/search/SearchResultLayoutView.js
index 90c337671..160736971 100644
--- a/dashboardv2/public/js/views/search/SearchResultLayoutView.js
+++ b/dashboardv2/public/js/views/search/SearchResultLayoutView.js
@@ -1372,6 +1372,7 @@ define(['require',
                     var view = new CreateEntityLayoutView({
                         entityDefCollection: that.entityDefCollection,
                         typeHeaders: that.typeHeaders,
+                        enumDefCollection: that.enumDefCollection,
                         searchVent: that.searchVent,
                         callback: function() {
                             that.fetchCollection();

Reply via email to