Repository: open-metadata/OpenMetadata
Pull request #20531: Update column config to store user preference in localStorage
Author: login: sweta1308
Labels: name: UI
description: UI specific issues
name: safe to test
description: Add this label to run secure Github workflows on PRs
Created: 2025-03-31T13:57:11Z | Merged: 2025-04-04T09:59:39Z

**Before:** 


https://github.com/user-attachments/assets/36ebd44c-b63b-407e-a240-4fad36f92f66


**After:**


https://github.com/user-attachments/assets/a86d9b0c-567d-44ef-839e-ab78e3d5db76



<!-- For frontend related change, please add screenshots and/or videos of your changes preview! -->

#
### Type of change:
<!-- You should choose 1 option and delete options that aren't relevant -->
- [ ] Bug fix
- [x] Improvement
- [ ] New feature
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation

#
### Checklist:
<!-- add an x in [] if done, don't mark items that you didn't do !-->
- [x] I have read the [**CONTRIBUTING**](https://docs.open-metadata.org/developers/contribute) document.
- [ ] My PR title is `Fixes <issue-number>: <short explanation>`
- [ ] I have commented on my code, particularly in hard-to-understand areas. 
- [ ] For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

<!-- Based on the type(s) of your change, uncomment the required checklist 👇 -->

<!-- Bug fix
- [ ] I have added a test that covers the exact scenario we are fixing. For complex issues, comment the issue number in the test for future reference.
-->

<!-- Improvement
- [ ] I have added tests around the new logic.
- [ ] For connector/ingestion changes: I updated the documentation.
-->

<!-- New feature
- [ ] The issue properly describes why the new feature is needed, what's the goal, and how we are building it. Any discussion
    or decision-making process is reflected in the issue.
- [ ] I have updated the documentation.
- [ ] I have added tests around the new logic.
-->

<!-- Breaking change
- [ ] I have added the tag `Backward-Incompatible-Change`.
-->



Diff:
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx
index a3a0ecebc082..85d3ef092813 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Container/ContainerDataModel/ContainerDataModel.test.tsx
@@ -96,6 +96,26 @@ jest.mock('../../../utils/GlossaryUtils', () => ({
   getGlossaryTermHierarchy: jest.fn().mockReturnValue([]),
 }));
 
+jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({
+  useGenericContext: jest.fn().mockReturnValue({
+    type: 'container',
+  }),
+}));
+
+jest.mock('../../../utils/TableUtils', () => ({
+  getTableExpandableConfig: jest.fn(),
+  getTableColumnConfigSelections: jest
+    .fn()
+    .mockReturnValue([
+      'name',
+      'description',
+      'dataTypeDisplay',
+      'tags',
+      'glossary',
+    ]),
+  handleUpdateTableColumnSelections: jest.fn(),
+}));
+
 jest.mock('../../../utils/TableTags/TableTags.utils', () => ({
   ...jest.requireActual('../../../utils/TableTags/TableTags.utils'),
   getFilterTags: jest.fn().mockReturnValue({
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx
index fbb6c7242fde..cdc1738190f9 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx
@@ -71,6 +71,7 @@ const mockGenericContextProps = {
     tableConstraints: mockTableConstraints,
   } as Table,
   permissions: DEFAULT_ENTITY_PERMISSION,
+  type: 'table',
 };
 
 jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({
@@ -79,6 +80,37 @@ jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({
     .mockImplementation(() => mockGenericContextProps),
 }));
 
+jest.mock('../../../utils/TableUtils', () => ({
+  ...jest.requireActual('../../../utils/TableUtils'),
+  getTableExpandableConfig: jest.fn().mockImplementation(() => ({
+    expandIcon: jest.fn(({ onExpand, expandable, record }) =>
+      expandable ? (
+        <button data-testid="expand-icon" onClick={(e) => onExpand(record, e)}>
+          ExpandIcon
+        </button>
+      ) : null
+    ),
+  })),
+  getTableColumnConfigSelections: jest
+    .fn()
+    .mockReturnValue([
+      'name',
+      'description',
+      'dataTypeDisplay',
+      'tags',
+      'glossary',
+    ]),
+  handleUpdateTableColumnSelections: jest
+    .fn()
+    .mockReturnValue([
+      'name',
+      'description',
+      'dataTypeDisplay',
+      'tags',
+      'glossary',
+    ]),
+}));
+
 const columnsWithDisplayName = [
   {
     name: 'comments',
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx
index b09cf7ac1ef0..69742afaeefa 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.test.tsx
@@ -53,6 +53,13 @@ jest.mock('../../common/RichTextEditor/RichTextEditorPreviewNew', () =>
       <p data-testid="description">{markdown}</p>
     ))
 );
+jest.mock('../../../utils/TableUtils', () => ({
+  getTableExpandableConfig: jest.fn(),
+  getTableColumnConfigSelections: jest
+    .fn()
+    .mockReturnValue(['name', 'description', 'owners']),
+  handleUpdateTableColumnSelections: jest.fn(),
+}));
 jest.mock('../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () =>
   jest
     .fn()
@@ -81,6 +88,7 @@ jest.mock('../useGlossary.store', () => ({
 jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({
   useGenericContext: jest.fn().mockImplementation(() => ({
     permissions: MOCK_PERMISSIONS,
+    type: 'glossary',
   })),
 }));
 
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Topic/TopicSchema/TopicSchema.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Topic/TopicSchema/TopicSchema.test.tsx
index 8c31121bed63..2923c8c76a6d 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Topic/TopicSchema/TopicSchema.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Topic/TopicSchema/TopicSchema.test.tsx
@@ -42,6 +42,25 @@ jest.mock('../../Database/TableDescription/TableDescription.component', () =>
   ))
 );
 
+jest.mock('../../../utils/TableUtils', () => ({
+  ...jest.requireActual('../../../utils/TableUtils'),
+  getTableExpandableConfig: jest.fn().mockImplementation(() => ({
+    expandIcon: jest.fn(({ onExpand, expandable, record }) =>
+      expandable ? (
+        <button data-testid="expand-icon" onClick={(e) => onExpand(record, e)}>
+          ExpandIcon
+        </button>
+      ) : null
+    ),
+  })),
+  getTableColumnConfigSelections: jest
+    .fn()
+    .mockReturnValue(['name', 'description', 'dataType', 'tags', 'glossary']),
+  handleUpdateTableColumnSelections: jest
+    .fn()
+    .mockReturnValue(['name', 'description', 'dataType', 'tags', 'glossary']),
+}));
+
 jest.mock('../../common/RichTextEditor/RichTextEditorPreviewerV1', () =>
   jest
     .fn()
@@ -128,6 +147,7 @@ jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({
       EditAll: true,
     },
     onUpdate: mockOnUpdate,
+    type: 'topic',
   })),
 }));
 
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ListView/ListView.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ListView/ListView.component.tsx
index 6fcaf419ae57..a7a13115435a 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/ListView/ListView.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ListView/ListView.component.tsx
@@ -21,6 +21,7 @@ import {
   COMMON_STATIC_TABLE_VISIBLE_COLUMNS,
   DEFAULT_SERVICE_VISIBLE_COLUMNS,
 } from '../../../constants/TableKeys.constants';
+import { EntityType } from '../../../enums/entity.enum';
 import NextPrevious from '../NextPrevious/NextPrevious';
 import Searchbar from '../SearchBarComponent/SearchBar.component';
 import Table from '../Table/Table';
@@ -98,6 +99,7 @@ export const ListView = <T extends object = any>({
           <Table
             customPaginationProps={customPaginationProps}
             defaultVisibleColumns={DEFAULT_SERVICE_VISIBLE_COLUMNS}
+            entityType={EntityType.SERVICE}
             staticVisibleColumns={COMMON_STATIC_TABLE_VISIBLE_COLUMNS}
             {...tableProps}
           />
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.interface.ts
index 00ed8e59e7a9..ea28fe8d04fa 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.interface.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.interface.ts
@@ -32,6 +32,7 @@ export interface TableComponentProps<T> extends TableProps<T> {
   customPaginationProps?: NextPreviousProps & {
     showPagination: boolean;
   };
+  entityType?: string;
 }
 
 export interface TableColumnDropdownList {
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
index 4bc7b5e5d47d..c131a341c1f8 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
@@ -36,11 +36,17 @@ import { DndProvider } from 'react-dnd';
 import { HTML5Backend } from 'react-dnd-html5-backend';
 import { useTranslation } from 'react-i18next';
 import { ReactComponent as ColumnIcon } from '../../../assets/svg/ic-column.svg';
+import { useApplicationStore } from '../../../hooks/useApplicationStore';
 import {
   getCustomizeColumnDetails,
   getReorderedColumns,
 } from '../../../utils/CustomizeColumnUtils';
-import { getTableExpandableConfig } from '../../../utils/TableUtils';
+import {
+  getTableColumnConfigSelections,
+  getTableExpandableConfig,
+  handleUpdateTableColumnSelections,
+} from '../../../utils/TableUtils';
+import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
 import Loader from '../Loader/Loader';
 import NextPrevious from '../NextPrevious/NextPrevious';
 import Searchbar from '../SearchBarComponent/SearchBar.component';
@@ -54,10 +60,19 @@ import './table.less';
 type TableProps<T extends Record<string, unknown>> = TableComponentProps<T>;
 
 const Table = <T extends Record<string, unknown>>(
-  { loading, searchProps, customPaginationProps, ...rest }: TableProps<T>,
+  {
+    loading,
+    searchProps,
+    customPaginationProps,
+    entityType,
+    defaultVisibleColumns,
+    ...rest
+  }: TableProps<T>,
   ref: Ref<HTMLDivElement> | null | undefined
 ) => {
   const { t } = useTranslation();
+  const { type } = useGenericContext();
+  const { currentUser } = useApplicationStore();
   const [propsColumns, setPropsColumns] = useState<ColumnType<T>[]>([]);
   const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
   const [dropdownColumnList, setDropdownColumnList] = useState<
@@ -65,7 +80,7 @@ const Table = <T extends Record<string, unknown>>(
   >([]);
   const [columnDropdownSelections, setColumnDropdownSelections] = useState<
     string[]
-  >(rest.defaultVisibleColumns ?? []);
+  >([]);
   const { resizableColumns, components, tableWidth } = useAntdColumnResize(
     () => ({ columns: propsColumns, minWidth: 80 }),
     [propsColumns]
@@ -76,11 +91,12 @@ const Table = <T extends Record<string, unknown>>(
     [loading]
   );
 
+  const entityKey = useMemo(() => type ?? entityType, [type, entityType]);
+
   // Check if the table is in Full View mode, if so, the dropdown and Customize Column feature is not available
   const isFullViewTable = useMemo(
-    () =>
-      isEmpty(rest.staticVisibleColumns) && isEmpty(rest.defaultVisibleColumns),
-    [rest.staticVisibleColumns, rest.defaultVisibleColumns]
+    () => isEmpty(rest.staticVisibleColumns) && isEmpty(defaultVisibleColumns),
+    [rest.staticVisibleColumns, defaultVisibleColumns]
   );
 
   const handleMoveItem = useCallback(
@@ -93,11 +109,17 @@ const Table = <T extends Record<string, unknown>>(
 
   const handleColumnItemSelect = useCallback(
     (key: string, selected: boolean) => {
-      setColumnDropdownSelections((prev: string[]) => {
-        return selected ? [...prev, key] : prev.filter((item) => item !== key);
-      });
+      const updatedSelections = handleUpdateTableColumnSelections(
+        selected,
+        key,
+        columnDropdownSelections,
+        currentUser?.fullyQualifiedName ?? '',
+        entityKey
+      );
+
+      setColumnDropdownSelections(updatedSelections);
     },
-    [setColumnDropdownSelections]
+    [columnDropdownSelections, entityKey]
   );
 
   const handleBulkColumnAction = useCallback(() => {
@@ -206,11 +228,22 @@ const Table = <T extends Record<string, unknown>>(
     rest.staticVisibleColumns,
   ]);
 
+  useEffect(() => {
+    const selections = getTableColumnConfigSelections(
+      currentUser?.fullyQualifiedName ?? '',
+      entityKey,
+      isFullViewTable,
+      defaultVisibleColumns
+    );
+
+    setColumnDropdownSelections(selections);
+  }, [entityKey, defaultVisibleColumns, isFullViewTable]);
+
   return (
     <Row className={classNames('table-container', rest.containerClassName)}>
       <Col
         className={classNames({
-          'p-y-md': searchProps || rest.extraTableFilters || !isFullViewTable,
+          'p-y-md': searchProps ?? rest.extraTableFilters ?? !isFullViewTable,
         })}
         span={24}>
         <Row className="p-x-md">
diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts
index d7c96c4dc743..38c616f9e8af 100644
--- a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts
@@ -76,6 +76,7 @@ export enum EntityType {
   API_ENDPOINT = 'apiEndpoint',
   METRIC = 'metric',
   WORKFLOW_DEFINITION = 'workflowDefinition',
+  SERVICE = 'service',
 }
 
 export enum EntityLineageDirection {
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexFieldsTable/SearchIndexFieldsTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexFieldsTable/SearchIndexFieldsTable.test.tsx
index 5c81d3b8c808..7e3e2dcb7b35 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexFieldsTable/SearchIndexFieldsTable.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexFieldsTable/SearchIndexFieldsTable.test.tsx
@@ -47,6 +47,15 @@ jest.mock(
     jest.fn().mockImplementation(() => <div>testFilterTablePlaceHolder</div>)
 );
 
+jest.mock(
+  '../../../components/Customization/GenericProvider/GenericProvider',
+  () => ({
+    useGenericContext: jest.fn().mockReturnValue({
+      type: 'searchIndex',
+    }),
+  })
+);
+
 jest.mock(
   '../../../components/Database/TableDescription/TableDescription.component',
   () => jest.fn().mockImplementation(() => <div>testTableDescription</div>)
@@ -76,6 +85,16 @@ jest.mock('../../../utils/TableUtils', () => ({
   updateFieldDescription: jest.fn(),
   updateFieldTags: jest.fn(),
   getTableExpandableConfig: jest.fn().mockReturnValue({}),
+  getTableColumnConfigSelections: jest
+    .fn()
+    .mockReturnValue([
+      'name',
+      'description',
+      'dataTypeDisplay',
+      'tags',
+      'glossary',
+    ]),
+  handleUpdateTableColumnSelections: jest.fn(),
 }));
 
 describe('SearchIndexFieldsTable component', () => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx
index 0b6d200e4e4a..8ccfe4528a4c 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx
@@ -132,7 +132,7 @@ function ServiceMainTabContent({
   const handleDescriptionUpdate = useCallback(async (updatedHTML: string) => {
     try {
       await onDescriptionUpdate(updatedHTML);
-    } catch (e) {
+    } catch {
       // Error
     }
   }, []);
@@ -146,7 +146,7 @@ function ServiceMainTabContent({
         }
         const updatedData = {
           ...pageDataDetails,
-          displayName: entityData.displayName || undefined,
+          displayName: entityData.displayName ?? undefined,
         };
         const jsonPatch = compare(pageDataDetails, updatedData);
         const response = await callServicePatchAPI(
@@ -282,6 +282,7 @@ function ServiceMainTabContent({
                           defaultVisibleColumns={
                             DEFAULT_SERVICE_TAB_VISIBLE_COLUMNS
                           }
+                          entityType={serviceCategory}
                           extraTableFilters={
                             <>
                               <span>
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ServiceVersionPage/ServiceVersionMainTabContent.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ServiceVersionPage/ServiceVersionMainTabContent.tsx
index 11c383f12f07..338a26b71e91 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/ServiceVersionPage/ServiceVersionMainTabContent.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/ServiceVersionPage/ServiceVersionMainTabContent.tsx
@@ -87,6 +87,7 @@ function ServiceVersionMainTabContent({
                 }}
                 data-testid="service-children-table"
                 dataSource={data}
+                entityType={entityType}
                 loading={isServiceLoading}
                 locale={{
                   emptyText: <ErrorPlaceHolder className="m-y-md" />,
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedureTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedureTab.test.tsx
index 4fd4b4e63ef2..5e2a72378a62 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedureTab.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/StoredProcedure/StoredProcedureTab.test.tsx
@@ -68,6 +68,10 @@ jest.mock('../../utils/StringsUtils', () => ({
 
 jest.mock('../../utils/TableUtils', () => ({
   getTableExpandableConfig: jest.fn(),
+  getTableColumnConfigSelections: jest
+    .fn()
+    .mockReturnValue(['name', 'description']),
+  handleUpdateTableColumnSelections: jest.fn(),
 }));
 
 jest.mock('../../rest/storedProceduresAPI', () => {
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
index 38bd153786ec..28b2c25286b4 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
@@ -704,7 +704,7 @@ export const getUpdatedTags = (newFieldTags: Array<EntityTags>): TagLabel[] => {
     ...omit(tag, 'isRemovable'),
     labelType: LabelType.Manual,
     state: State.Confirmed,
-    source: tag.source || 'Classification',
+    source: tag.source ?? 'Classification',
     tagFQN: tag.tagFQN,
   }));
 
@@ -747,6 +747,66 @@ export const updateFieldDescription = <T extends TableFieldsInfoCommonEntities>(
   });
 };
 
+export const getTableColumnConfigSelections = (
+  userFqn: string,
+  entityType: string | undefined,
+  isFullViewTable: boolean,
+  defaultColumns: string[] | undefined
+) => {
+  if (!userFqn) {
+    return [];
+  }
+
+  const storageKey = `selectedColumns-${userFqn}`;
+  const selectedColumns = JSON.parse(localStorage.getItem(storageKey) ?? '{}');
+
+  if (entityType) {
+    if (selectedColumns[entityType]) {
+      return selectedColumns[entityType];
+    } else if (!isFullViewTable) {
+      localStorage.setItem(
+        storageKey,
+        JSON.stringify({
+          ...selectedColumns,
+          [entityType]: defaultColumns,
+        })
+      );
+
+      return defaultColumns;
+    }
+  }
+
+  return [];
+};
+
+export const handleUpdateTableColumnSelections = (
+  selected: boolean,
+  key: string,
+  columnDropdownSelections: string[],
+  userFqn: string,
+  entityType: string | undefined
+) => {
+  const updatedSelections = selected
+    ? [...columnDropdownSelections, key]
+    : columnDropdownSelections.filter((item) => item !== key);
+
+  // Updating localStorage
+  const selectedColumns = JSON.parse(
+    localStorage.getItem(`selectedColumns-${userFqn}`) ?? '{}'
+  );
+  if (entityType) {
+    localStorage.setItem(
+      `selectedColumns-${userFqn}`,
+      JSON.stringify({
+        ...selectedColumns,
+        [entityType]: updatedSelections,
+      })
+    );
+  }
+
+  return updatedSelections;
+};
+
 export const getTableDetailPageBaseTabs = ({
   queryCount,
   isTourOpen,
@@ -910,7 +970,7 @@ export const getTableDetailPageBaseTabs = ({
       children: (
         <QueryViewer
           sqlQuery={
-            get(tableDetails, 'dataModel.sql', '') ||
+            get(tableDetails, 'dataModel.sql', '') ??
             get(tableDetails, 'dataModel.rawSql', '')
           }
           title={


Reviews:
user: login: chirag-madlani
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T12:16:21Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T12:18:25Z
user: login: Ashish8689
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:48:12Z
user: login: Ashish8689
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T14:41:11Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:02:04Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:02:12Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T15:02:30Z
user: login: chirag-madlani
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-02T15:16:43Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T18:00:55Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T18:01:04Z
user: login: chirag-madlani
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-03T04:47:31Z
user: login: chirag-madlani
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T04:48:31Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:19:03Z
user: login: sweta1308
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:19:08Z
user: login: chirag-madlani
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-04T07:26:19Z
user: login: chirag-madlani
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-04T09:51:39Z

Review comments:
diff_hunk: @@ -98,6 +99,7 @@ export const ListView = <T extends object = any>({
           <Table
             customPaginationProps={customPaginationProps}
             defaultVisibleColumns={DEFAULT_SERVICE_VISIBLE_COLUMNS}
+            entityType={EntityType.SERVICE}
path: openmetadata-ui/src/main/resources/ui/src/components/common/ListView/ListView.component.tsx
user: login: chirag-madlani
body: Do we need this?
created_at: 2025-04-02T12:14:52Z
author_association: COLLABORATOR
diff_hunk: @@ -206,11 +234,33 @@ const Table = <T extends Record<string, unknown>>(
     rest.staticVisibleColumns,
   ]);
 
+  useEffect(() => {
+    const storageKey = `selectedColumns-${currentUser?.fullyQualifiedName}`;
+    const selectedColumns = JSON.parse(
+      localStorage.getItem(storageKey) ?? '{}'
+    );
+
+    if (type || entityType) {
+      if (selectedColumns[type ?? entityType]) {
+        setColumnDropdownSelections(selectedColumns[type ?? entityType]);
+      } else if (defaultVisibleColumns) {
+        setColumnDropdownSelections(defaultVisibleColumns);
+        localStorage.setItem(
+          storageKey,
+          JSON.stringify({
+            ...selectedColumns,
+            [type ?? entityType]: defaultVisibleColumns,
+          })
+        );
+      }
+    }
+  }, [type, entityType, defaultVisibleColumns]);
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: chirag-madlani
body: I think this can be a util and also let's do this based on some flag instead default 
created_at: 2025-04-02T12:15:56Z
author_association: COLLABORATOR
diff_hunk: @@ -98,6 +99,7 @@ export const ListView = <T extends object = any>({
           <Table
             customPaginationProps={customPaginationProps}
             defaultVisibleColumns={DEFAULT_SERVICE_VISIBLE_COLUMNS}
+            entityType={EntityType.SERVICE}
path: openmetadata-ui/src/main/resources/ui/src/components/common/ListView/ListView.component.tsx
user: login: sweta1308
body: Yes, as service page doesn't have the Generic support.
created_at: 2025-04-02T12:18:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -277,7 +327,7 @@ const Table = <T extends Record<string, unknown>>(
           {...resizingTableProps}
           scroll={{
             y: 740,
-            x: resizingTableProps.scroll?.x || rest.scroll?.x,
+            x: resizingTableProps.scroll?.x ?? rest.scroll?.x,
           }}
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: Ashish8689
body: since it will be number and  ?? operator check for null and undefined. So even in the case of  `resizingTableProps.scroll?.x` is 0, it won't fallback ti rest one
created_at: 2025-04-02T13:40:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -235,26 +243,16 @@ const Table = <T extends Record<string, unknown>>(
   ]);
 
   useEffect(() => {
-    const storageKey = `selectedColumns-${currentUser?.fullyQualifiedName}`;
-    const selectedColumns = JSON.parse(
-      localStorage.getItem(storageKey) ?? '{}'
+    const selections = getColumnSelections(
+      currentUser?.fullyQualifiedName ?? '',
+      type,
+      entityType,
+      isDefaultColumns,
+      defaultVisibleColumns
     );
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: Ashish8689
body: `isDefaultColumns` is derived from `defaultVisibleColumns`,  we can only use the `defaultVisibleColumns` to perform our function!
created_at: 2025-04-02T14:41:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -235,26 +243,16 @@ const Table = <T extends Record<string, unknown>>(
   ]);
 
   useEffect(() => {
-    const storageKey = `selectedColumns-${currentUser?.fullyQualifiedName}`;
-    const selectedColumns = JSON.parse(
-      localStorage.getItem(storageKey) ?? '{}'
+    const selections = getColumnSelections(
+      currentUser?.fullyQualifiedName ?? '',
+      type,
+      entityType,
+      isDefaultColumns,
+      defaultVisibleColumns
     );
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: sweta1308
body: updated
created_at: 2025-04-02T15:02:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -277,7 +327,7 @@ const Table = <T extends Record<string, unknown>>(
           {...resizingTableProps}
           scroll={{
             y: 740,
-            x: resizingTableProps.scroll?.x || rest.scroll?.x,
+            x: resizingTableProps.scroll?.x ?? rest.scroll?.x,
           }}
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: sweta1308
body: updated
created_at: 2025-04-02T15:02:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -206,11 +234,33 @@ const Table = <T extends Record<string, unknown>>(
     rest.staticVisibleColumns,
   ]);
 
+  useEffect(() => {
+    const storageKey = `selectedColumns-${currentUser?.fullyQualifiedName}`;
+    const selectedColumns = JSON.parse(
+      localStorage.getItem(storageKey) ?? '{}'
+    );
+
+    if (type || entityType) {
+      if (selectedColumns[type ?? entityType]) {
+        setColumnDropdownSelections(selectedColumns[type ?? entityType]);
+      } else if (defaultVisibleColumns) {
+        setColumnDropdownSelections(defaultVisibleColumns);
+        localStorage.setItem(
+          storageKey,
+          JSON.stringify({
+            ...selectedColumns,
+            [type ?? entityType]: defaultVisibleColumns,
+          })
+        );
+      }
+    }
+  }, [type, entityType, defaultVisibleColumns]);
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: sweta1308
body: Updated
created_at: 2025-04-02T15:02:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -206,11 +237,23 @@ const Table = <T extends Record<string, unknown>>(
     rest.staticVisibleColumns,
   ]);
 
+  useEffect(() => {
+    const selections = getColumnSelections(
+      currentUser?.fullyQualifiedName ?? '',
+      type,
+      entityType,
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: chirag-madlani
body: You can club this while passing why you need both argument for function?
created_at: 2025-04-02T15:15:51Z
author_association: COLLABORATOR
diff_hunk: @@ -93,11 +106,29 @@ const Table = <T extends Record<string, unknown>>(
 
   const handleColumnItemSelect = useCallback(
     (key: string, selected: boolean) => {
-      setColumnDropdownSelections((prev: string[]) => {
-        return selected ? [...prev, key] : prev.filter((item) => item !== key);
-      });
+      const updatedSelections = selected
+        ? [...columnDropdownSelections, key]
+        : columnDropdownSelections.filter((item) => item !== key);
+
+      // Updating localStorage
+      const selectedColumns = JSON.parse(
+        localStorage.getItem(
+          `selectedColumns-${currentUser?.fullyQualifiedName}`
+        ) ?? '{}'
+      );
+      if (type || entityType) {
+        localStorage.setItem(
+          `selectedColumns-${currentUser?.fullyQualifiedName}`,
+          JSON.stringify({
+            ...selectedColumns,
+            [type ?? entityType]: updatedSelections,
+          })
+        );
+      }
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: chirag-madlani
body: move this to util as well
created_at: 2025-04-02T15:16:25Z
author_association: COLLABORATOR
diff_hunk: @@ -93,11 +106,29 @@ const Table = <T extends Record<string, unknown>>(
 
   const handleColumnItemSelect = useCallback(
     (key: string, selected: boolean) => {
-      setColumnDropdownSelections((prev: string[]) => {
-        return selected ? [...prev, key] : prev.filter((item) => item !== key);
-      });
+      const updatedSelections = selected
+        ? [...columnDropdownSelections, key]
+        : columnDropdownSelections.filter((item) => item !== key);
+
+      // Updating localStorage
+      const selectedColumns = JSON.parse(
+        localStorage.getItem(
+          `selectedColumns-${currentUser?.fullyQualifiedName}`
+        ) ?? '{}'
+      );
+      if (type || entityType) {
+        localStorage.setItem(
+          `selectedColumns-${currentUser?.fullyQualifiedName}`,
+          JSON.stringify({
+            ...selectedColumns,
+            [type ?? entityType]: updatedSelections,
+          })
+        );
+      }
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: sweta1308
body: Updated
created_at: 2025-04-02T18:00:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -206,11 +237,23 @@ const Table = <T extends Record<string, unknown>>(
     rest.staticVisibleColumns,
   ]);
 
+  useEffect(() => {
+    const selections = getColumnSelections(
+      currentUser?.fullyQualifiedName ?? '',
+      type,
+      entityType,
path: openmetadata-ui/src/main/resources/ui/src/components/common/Table/Table.tsx
user: login: sweta1308
body: Updated
created_at: 2025-04-02T18:01:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -747,6 +747,66 @@ export const updateFieldDescription = <T extends TableFieldsInfoCommonEntities>(
   });
 };
 
+export const getColumnSelections = (
path: openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
user: login: chirag-madlani
body: @sweta1308 give some meaning full name as it's on util and not within component context 
created_at: 2025-04-03T04:48:24Z
author_association: COLLABORATOR
diff_hunk: @@ -747,6 +747,66 @@ export const updateFieldDescription = <T extends TableFieldsInfoCommonEntities>(
   });
 };
 
+export const getColumnSelections = (
+  userFqn: string,
+  entityType: string | undefined,
+  isFullViewTable: boolean,
+  defaultColumns: string[] | undefined
+) => {
+  if (!userFqn) {
+    return [];
+  }
+
+  const storageKey = `selectedColumns-${userFqn}`;
+  const selectedColumns = JSON.parse(localStorage.getItem(storageKey) ?? '{}');
+
+  if (entityType) {
+    if (selectedColumns[entityType]) {
+      return selectedColumns[entityType];
+    } else if (!isFullViewTable) {
+      localStorage.setItem(
+        storageKey,
+        JSON.stringify({
+          ...selectedColumns,
+          [entityType]: defaultColumns,
+        })
+      );
+
+      return defaultColumns;
+    }
+  }
+
+  return [];
+};
+
+export const handleColumnSelections = (
path: openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
user: login: chirag-madlani
body: Same
created_at: 2025-04-03T04:48:28Z
author_association: COLLABORATOR
diff_hunk: @@ -747,6 +747,66 @@ export const updateFieldDescription = <T extends TableFieldsInfoCommonEntities>(
   });
 };
 
+export const getColumnSelections = (
path: openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
user: login: sweta1308
body: updated
created_at: 2025-04-03T09:19:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -747,6 +747,66 @@ export const updateFieldDescription = <T extends TableFieldsInfoCommonEntities>(
   });
 };
 
+export const getColumnSelections = (
+  userFqn: string,
+  entityType: string | undefined,
+  isFullViewTable: boolean,
+  defaultColumns: string[] | undefined
+) => {
+  if (!userFqn) {
+    return [];
+  }
+
+  const storageKey = `selectedColumns-${userFqn}`;
+  const selectedColumns = JSON.parse(localStorage.getItem(storageKey) ?? '{}');
+
+  if (entityType) {
+    if (selectedColumns[entityType]) {
+      return selectedColumns[entityType];
+    } else if (!isFullViewTable) {
+      localStorage.setItem(
+        storageKey,
+        JSON.stringify({
+          ...selectedColumns,
+          [entityType]: defaultColumns,
+        })
+      );
+
+      return defaultColumns;
+    }
+  }
+
+  return [];
+};
+
+export const handleColumnSelections = (
path: openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx
user: login: sweta1308
body: updated
created_at: 2025-04-03T09:19:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-31T14:19:31Z
body: <!-- Jest Coverage Comment: ui-coverage-tests -->
# Jest test Coverage

## UI tests summary

| Lines | Statements | Branches | Functions |
| --- | --- | --- | --- |
| <a href="https://github.com/open-metadata/OpenMetadata/blob//README.md"><img alt="Coverage: 63%" src="https://img.shields.io/badge/Coverage-63%25-yellow.svg" /></a><br/> | 63.26% (44326/70073) | 38.4% (17714/46132) | 42.83% (5276/12319) |

author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-04T08:49:38Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=open-metadata-ui&pullRequest=20531) **Quality Gate passed for 'open-metadata-ui'**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=open-metadata-ui&pullRequest=20531&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=open-metadata-ui&pullRequest=20531&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=open-metadata-ui&pullRequest=20531&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-metadata-ui&pullRequest=20531&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=open-metadata-ui&pullRequest=20531&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=open-metadata-ui&pullRequest=20531)


author_association: NONE