From 839b713f50e2c1696923dc3e1cf0b5091537272f Mon Sep 17 00:00:00 2001
From: Denis Berezin <denis.berezin@metabase.com>
Date: Tue, 5 Nov 2024 19:23:14 +0300
Subject: [PATCH] feat(sdk): Allow to hide columns in CollectionBrowser
 (#49449)

* Add possibility to hide columns in CollectionBrowser

* Add tests

* Review fixes

* Fix e2e
---
 .../CollectionBrowser.stories.tsx             | 32 +++++++
 .../CollectionBrowser/CollectionBrowser.tsx   | 17 +++-
 .../CollectionBrowser.unit.spec.tsx           | 96 +++++++++++++++++++
 .../test/server-mocks/sdk-init.ts             |  2 +-
 .../CollectionContentView.tsx                 | 15 ++-
 .../CollectionItemsTable.tsx                  | 22 ++++-
 .../components/CollectionContent/constants.ts | 24 +++++
 .../ItemsTable/BaseItemTableRow.tsx           | 10 +-
 .../BaseItemsTable/BaseItemsTable.tsx         | 65 +++++++------
 .../BaseItemsTable.unit.spec.tsx              |  5 +
 .../BaseItemsTableBody/BaseItemsTableBody.tsx |  6 +-
 .../ItemsTable/DefaultItemRenderer.tsx        | 38 +++++---
 .../metabase/components/ItemsTable/utils.ts   | 12 +++
 .../containers/dnd/ItemsDragLayer.jsx         |  5 +-
 14 files changed, 291 insertions(+), 58 deletions(-)
 create mode 100644 enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.stories.tsx
 create mode 100644 enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.unit.spec.tsx

diff --git a/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.stories.tsx b/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.stories.tsx
new file mode 100644
index 00000000000..4207a2be5d0
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.stories.tsx
@@ -0,0 +1,32 @@
+import type { StoryFn } from "@storybook/react";
+import type { ComponentProps } from "react";
+
+import { CollectionBrowser } from "embedding-sdk";
+import { CommonSdkStoryWrapper } from "embedding-sdk/test/CommonSdkStoryWrapper";
+
+export default {
+  title: "EmbeddingSDK/CollectionBrowser",
+  component: CollectionBrowser,
+  parameters: {
+    layout: "fullscreen",
+  },
+  decorators: [CommonSdkStoryWrapper],
+};
+
+const Template: StoryFn<ComponentProps<typeof CollectionBrowser>> = args => {
+  return <CollectionBrowser {...args} />;
+};
+
+export const Default = {
+  render: Template,
+
+  args: {},
+};
+
+export const WithTypeAndNameColumn = {
+  render: Template,
+
+  args: {
+    visibleColumns: ["type", "name"],
+  },
+};
diff --git a/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.tsx b/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.tsx
index 8ce98b4b34f..407f95052eb 100644
--- a/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.tsx
+++ b/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.tsx
@@ -23,6 +23,19 @@ const USER_FACING_ENTITY_NAMES = [
 
 type UserFacingEntityName = (typeof USER_FACING_ENTITY_NAMES)[number];
 
+type CollectionBrowserListColumns =
+  | "type"
+  | "name"
+  | "lastEditedBy"
+  | "lastEditedAt";
+
+const COLLECTION_BROWSER_LIST_COLUMNS: CollectionBrowserListColumns[] = [
+  "type",
+  "name",
+  "lastEditedBy",
+  "lastEditedAt",
+];
+
 const ENTITY_NAME_MAP: Partial<
   Record<UserFacingEntityName, CollectionItemModel>
 > = {
@@ -38,6 +51,7 @@ type CollectionBrowserProps = {
   pageSize?: number;
   visibleEntityTypes?: UserFacingEntityName[];
   EmptyContentComponent?: ComponentType | null;
+  visibleColumns?: CollectionBrowserListColumns[];
   className?: string;
   style?: CSSProperties;
 };
@@ -48,6 +62,7 @@ export const CollectionBrowserInner = ({
   pageSize = COLLECTION_PAGE_SIZE,
   visibleEntityTypes = [...USER_FACING_ENTITY_NAMES],
   EmptyContentComponent = null,
+  visibleColumns = COLLECTION_BROWSER_LIST_COLUMNS,
   className,
   style,
 }: CollectionBrowserProps) => {
@@ -85,7 +100,7 @@ export const CollectionBrowserInner = ({
         onClick={onClickItem}
         pageSize={pageSize}
         models={collectionTypes}
-        showActionMenu={false}
+        visibleColumns={visibleColumns}
         EmptyContentComponent={EmptyContentComponent ?? undefined}
       />
     </Stack>
diff --git a/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.unit.spec.tsx b/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.unit.spec.tsx
new file mode 100644
index 00000000000..07632d87091
--- /dev/null
+++ b/enterprise/frontend/src/embedding-sdk/components/public/CollectionBrowser/CollectionBrowser.unit.spec.tsx
@@ -0,0 +1,96 @@
+import type { ComponentProps } from "react";
+
+import {
+  setupCollectionItemsEndpoint,
+  setupCollectionsEndpoints,
+} from "__support__/server-mocks";
+import { renderWithProviders, screen, within } from "__support__/ui";
+import { CollectionBrowserInner } from "embedding-sdk/components/public/CollectionBrowser/CollectionBrowser";
+import { createMockJwtConfig } from "embedding-sdk/test/mocks/config";
+import { setupSdkState } from "embedding-sdk/test/server-mocks/sdk-init";
+import { ROOT_COLLECTION } from "metabase/entities/collections";
+import {
+  createMockCollection,
+  createMockCollectionItem,
+} from "metabase-types/api/mocks";
+
+const BOBBY_TEST_COLLECTION = createMockCollection({
+  archived: false,
+  can_write: true,
+  description: null,
+  id: 1,
+  location: "/",
+  name: "Bobby Tables's Personal Collection",
+  personal_owner_id: 100,
+});
+
+const ROOT_TEST_COLLECTION = createMockCollection({
+  ...ROOT_COLLECTION,
+  can_write: false,
+  effective_ancestors: [],
+  id: "root",
+});
+
+const TEST_COLLECTIONS = [ROOT_TEST_COLLECTION, BOBBY_TEST_COLLECTION];
+
+describe("CollectionBrowser", () => {
+  it("should render", async () => {
+    await setup();
+
+    expect(screen.getByText("Type")).toBeInTheDocument();
+    expect(screen.getByText("Name")).toBeInTheDocument();
+    expect(screen.getByText("Last edited by")).toBeInTheDocument();
+    expect(screen.getByText("Last edited at")).toBeInTheDocument();
+  });
+
+  it("should allow to hide certain columns", async () => {
+    await setup({
+      props: {
+        visibleColumns: ["type", "name"],
+      },
+    });
+
+    const columnNames: (string | null)[] = [];
+
+    within(screen.getByTestId("items-table-head"))
+      .getAllByRole("button")
+      .forEach(el => {
+        columnNames.push(el.textContent);
+      });
+
+    expect(columnNames).toStrictEqual(["Type", "Name"]);
+  });
+});
+
+async function setup({
+  props,
+}: {
+  props?: Partial<ComponentProps<typeof CollectionBrowserInner>>;
+} = {}) {
+  setupCollectionsEndpoints({
+    collections: TEST_COLLECTIONS,
+    rootCollection: ROOT_TEST_COLLECTION,
+  });
+
+  setupCollectionItemsEndpoint({
+    collection: ROOT_TEST_COLLECTION,
+    collectionItems: [
+      createMockCollectionItem({ id: 2, model: "dashboard" }),
+      createMockCollectionItem({ id: 3, model: "card" }),
+    ],
+  });
+
+  const state = setupSdkState();
+
+  renderWithProviders(<CollectionBrowserInner {...props} />, {
+    mode: "sdk",
+    sdkProviderProps: {
+      config: createMockJwtConfig({
+        jwtProviderUri: "http://TEST_URI/sso/metabase",
+      }),
+    },
+    storeInitialState: state,
+  });
+
+  expect(await screen.findByTestId("collection-table")).toBeInTheDocument();
+}
diff --git a/enterprise/frontend/src/embedding-sdk/test/server-mocks/sdk-init.ts b/enterprise/frontend/src/embedding-sdk/test/server-mocks/sdk-init.ts
index 669d0b332fc..b8fe8f7a1d2 100644
--- a/enterprise/frontend/src/embedding-sdk/test/server-mocks/sdk-init.ts
+++ b/enterprise/frontend/src/embedding-sdk/test/server-mocks/sdk-init.ts
@@ -37,7 +37,7 @@ export const setupSdkState = ({
   tokenFeatures?: TokenFeatures;
   settingDefinitions?: SettingDefinition[];
   sdkState?: SdkState;
-} & Partial<SdkStoreState>) => {
+} & Partial<SdkStoreState> = {}) => {
   const settingValuesWithToken = {
     ...settingValues,
     "token-features": tokenFeatures,
diff --git a/frontend/src/metabase/collections/components/CollectionContent/CollectionContentView.tsx b/frontend/src/metabase/collections/components/CollectionContent/CollectionContentView.tsx
index 795859eaeee..f328ccc398c 100644
--- a/frontend/src/metabase/collections/components/CollectionContent/CollectionContentView.tsx
+++ b/frontend/src/metabase/collections/components/CollectionContent/CollectionContentView.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
 import { useDropzone } from "react-dropzone";
 import { usePrevious } from "react-use";
 import { t } from "ttag";
@@ -7,6 +7,10 @@ import ErrorBoundary from "metabase/ErrorBoundary";
 import { deletePermanently } from "metabase/archive/actions";
 import { ArchivedEntityBanner } from "metabase/archive/components/ArchivedEntityBanner";
 import { CollectionBulkActions } from "metabase/collections/components/CollectionBulkActions";
+import {
+  type CollectionContentTableColumn,
+  DEFAULT_VISIBLE_COLUMNS_LIST,
+} from "metabase/collections/components/CollectionContent/constants";
 import PinnedItemOverview from "metabase/collections/components/PinnedItemOverview";
 import Header from "metabase/collections/containers/CollectionHeader";
 import type {
@@ -20,6 +24,7 @@ import {
   isRootTrashCollection,
   isTrashedCollection,
 } from "metabase/collections/utils";
+import { getVisibleColumnsMap } from "metabase/components/ItemsTable/utils";
 import ItemsDragLayer from "metabase/containers/dnd/ItemsDragLayer";
 import Bookmarks from "metabase/entities/bookmarks";
 import Collections from "metabase/entities/collections";
@@ -58,6 +63,7 @@ export const CollectionContentView = ({
   uploadFile,
   uploadsEnabled,
   canCreateUploadInDb,
+  visibleColumns = DEFAULT_VISIBLE_COLUMNS_LIST,
 }: {
   databases?: Database[];
   bookmarks?: Bookmark[];
@@ -70,6 +76,7 @@ export const CollectionContentView = ({
   uploadFile: UploadFile;
   uploadsEnabled: boolean;
   canCreateUploadInDb: boolean;
+  visibleColumns?: CollectionContentTableColumn[];
 }) => {
   const [isBookmarked, setIsBookmarked] = useState(false);
   const [selectedItems, setSelectedItems] = useState<CollectionItem[] | null>(
@@ -122,6 +129,11 @@ export const CollectionContentView = ({
 
   const dispatch = useDispatch();
 
+  const visibleColumnsMap = useMemo(
+    () => getVisibleColumnsMap(visibleColumns),
+    [visibleColumns],
+  );
+
   const onDrop = (acceptedFiles: File[]) => {
     if (!acceptedFiles.length) {
       dispatch(
@@ -299,6 +311,7 @@ export const CollectionContentView = ({
               selectedItems={selected}
               pinnedItems={pinnedItems}
               collection={collection}
+              visibleColumnsMap={visibleColumnsMap}
             />
           </CollectionRoot>
         );
diff --git a/frontend/src/metabase/collections/components/CollectionContent/CollectionItemsTable.tsx b/frontend/src/metabase/collections/components/CollectionContent/CollectionItemsTable.tsx
index 72d6ee3d4f2..c7bf39a3faa 100644
--- a/frontend/src/metabase/collections/components/CollectionContent/CollectionItemsTable.tsx
+++ b/frontend/src/metabase/collections/components/CollectionContent/CollectionItemsTable.tsx
@@ -1,10 +1,18 @@
 /* eslint-disable react/prop-types */
 import cx from "classnames";
-import { type ComponentType, useCallback, useEffect, useState } from "react";
+import {
+  type ComponentType,
+  useCallback,
+  useEffect,
+  useMemo,
+  useState,
+} from "react";
 
 import {
   ALL_MODELS,
   COLLECTION_PAGE_SIZE,
+  type CollectionContentTableColumn,
+  DEFAULT_VISIBLE_COLUMNS_LIST,
 } from "metabase/collections/components/CollectionContent/constants";
 import CollectionEmptyState from "metabase/collections/components/CollectionEmptyState";
 import type {
@@ -13,6 +21,7 @@ import type {
 } from "metabase/collections/types";
 import { isRootTrashCollection } from "metabase/collections/utils";
 import { ItemsTable } from "metabase/components/ItemsTable";
+import { getVisibleColumnsMap } from "metabase/components/ItemsTable/utils";
 import { PaginationControls } from "metabase/components/PaginationControls";
 import CS from "metabase/css/core/index.css";
 import Search from "metabase/entities/search";
@@ -68,7 +77,7 @@ export type CollectionItemsTableProps = {
   selected: CollectionItem[];
   toggleItem: (item: CollectionItem) => void;
   onClick: (item: CollectionItem) => void;
-  showActionMenu: boolean;
+  visibleColumns?: CollectionContentTableColumn[];
   EmptyContentComponent?: ComponentType<{
     collection?: Collection;
   }>;
@@ -105,7 +114,7 @@ export const CollectionItemsTable = ({
   pageSize = COLLECTION_PAGE_SIZE,
   models = ALL_MODELS,
   onClick,
-  showActionMenu = true,
+  visibleColumns = DEFAULT_VISIBLE_COLUMNS_LIST,
   EmptyContentComponent = DefaultEmptyContentComponent,
 }: CollectionItemsTableProps) => {
   const isEmbeddingSdk = useSelector(getIsEmbeddingSdk);
@@ -118,6 +127,11 @@ export const CollectionItemsTable = ({
   const { handleNextPage, handlePreviousPage, setPage, page, resetPage } =
     usePagination();
 
+  const visibleColumnsMap = useMemo(
+    () => getVisibleColumnsMap(visibleColumns),
+    [visibleColumns],
+  );
+
   useEffect(() => {
     if (collectionId) {
       resetPage();
@@ -208,7 +222,7 @@ export const CollectionItemsTable = ({
               onSelectAll={handleSelectAll}
               onSelectNone={clear}
               onClick={onClick}
-              showActionMenu={showActionMenu}
+              visibleColumnsMap={visibleColumnsMap}
             />
             <div className={cx(CS.flex, CS.justifyEnd, CS.my3)}>
               {hasPagination && (
diff --git a/frontend/src/metabase/collections/components/CollectionContent/constants.ts b/frontend/src/metabase/collections/components/CollectionContent/constants.ts
index 90f16158432..f40cde096b4 100644
--- a/frontend/src/metabase/collections/components/CollectionContent/constants.ts
+++ b/frontend/src/metabase/collections/components/CollectionContent/constants.ts
@@ -1,6 +1,30 @@
 import type { CollectionItemModel } from "metabase-types/api";
 
 export const COLLECTION_PAGE_SIZE = 25;
+
+export const COLLECTION_CONTENT_COLUMNS = [
+  "type",
+  "name",
+  "lastEditedBy",
+  "lastEditedAt",
+  "actionMenu",
+] as const;
+
+export type CollectionContentTableColumn =
+  (typeof COLLECTION_CONTENT_COLUMNS)[number];
+
+export type CollectionContentTableColumnsMap = {
+  [key in CollectionContentTableColumn]: true;
+};
+
+export const DEFAULT_VISIBLE_COLUMNS_LIST: CollectionContentTableColumn[] = [
+  "type",
+  "name",
+  "lastEditedBy",
+  "lastEditedAt",
+  "actionMenu",
+];
+
 export const ALL_MODELS: CollectionItemModel[] = [
   "dashboard",
   "dataset",
diff --git a/frontend/src/metabase/components/ItemsTable/BaseItemTableRow.tsx b/frontend/src/metabase/components/ItemsTable/BaseItemTableRow.tsx
index 698ffa04b9f..5dbd0eb66e3 100644
--- a/frontend/src/metabase/components/ItemsTable/BaseItemTableRow.tsx
+++ b/frontend/src/metabase/components/ItemsTable/BaseItemTableRow.tsx
@@ -28,7 +28,7 @@ type BaseItemTableRowProps = PropsWithChildren<
     | "onMove"
     | "onToggleSelected"
     | "onClick"
-    | "showActionMenu"
+    | "visibleColumnsMap"
   >
 >;
 
@@ -48,7 +48,7 @@ export const TableRow = ({
   itemKey,
   collection,
   onClick,
-  showActionMenu,
+  visibleColumnsMap,
 }: BaseItemTableRowProps) => (
   <tr key={itemKey} data-testid={testIdPrefix} style={{ height: 48 }}>
     <ItemComponent
@@ -65,7 +65,7 @@ export const TableRow = ({
       onCopy={onCopy}
       onMove={onMove}
       onToggleSelected={onToggleSelected}
-      showActionMenu={showActionMenu}
+      visibleColumnsMap={visibleColumnsMap}
     />
   </tr>
 );
@@ -88,7 +88,7 @@ export const ItemDragSourceTableRow = ({
   onClick,
   selectedItems,
   onDrop,
-  showActionMenu,
+  visibleColumnsMap,
 }: BaseItemTableRowProps) => {
   return (
     <ItemDragSource
@@ -115,7 +115,7 @@ export const ItemDragSourceTableRow = ({
           onMove={onMove}
           onToggleSelected={onToggleSelected}
           onClick={onClick}
-          showActionMenu={showActionMenu}
+          visibleColumnsMap={visibleColumnsMap}
         />
       </tr>
     </ItemDragSource>
diff --git a/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.tsx b/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.tsx
index ad4c20af8d3..1c95a75c8e2 100644
--- a/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.tsx
+++ b/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.tsx
@@ -1,6 +1,7 @@
 import type { HTMLAttributes, PropsWithChildren } from "react";
 import { useMemo } from "react";
 
+import type { CollectionContentTableColumnsMap } from "metabase/collections/components/CollectionContent";
 import type {
   CreateBookmark,
   DeleteBookmark,
@@ -119,7 +120,7 @@ export type BaseItemsTableProps = {
   ItemComponent?: (props: ItemRendererProps) => JSX.Element;
   includeColGroup?: boolean;
   onClick?: (item: CollectionItem) => void;
-  showActionMenu?: boolean;
+  visibleColumnsMap: CollectionContentTableColumnsMap;
 } & Partial<Omit<HTMLAttributes<HTMLTableElement>, "onCopy" | "onClick">>;
 
 export const BaseItemsTable = ({
@@ -145,7 +146,7 @@ export const BaseItemsTable = ({
   isInDragLayer = false,
   ItemComponent = DefaultItemRenderer,
   includeColGroup = true,
-  showActionMenu = true,
+  visibleColumnsMap,
   onClick,
   ...props
 }: BaseItemsTableProps) => {
@@ -158,11 +159,13 @@ export const BaseItemsTable = ({
       {includeColGroup && (
         <colgroup>
           {canSelect && <Columns.Select.Col />}
-          <Columns.Type.Col />
-          <Columns.Name.Col isInDragLayer={isInDragLayer} />
-          <Columns.LastEditedBy.Col />
-          <Columns.LastEditedAt.Col />
-          {showActionMenu && <Columns.ActionMenu.Col />}
+          {visibleColumnsMap["type"] && <Columns.Type.Col />}
+          {visibleColumnsMap["name"] && (
+            <Columns.Name.Col isInDragLayer={isInDragLayer} />
+          )}
+          {visibleColumnsMap["lastEditedBy"] && <Columns.LastEditedBy.Col />}
+          {visibleColumnsMap["lastEditedAt"] && <Columns.LastEditedAt.Col />}
+          {visibleColumnsMap["actionMenu"] && <Columns.ActionMenu.Col />}
           <Columns.RightEdge.Col />
         </colgroup>
       )}
@@ -181,25 +184,33 @@ export const BaseItemsTable = ({
                 onSelectNone={onSelectNone}
               />
             )}
-            <Columns.Type.Header
-              sortingOptions={sortingOptions}
-              onSortingOptionsChange={onSortingOptionsChange}
-            />
-            <Columns.Name.Header
-              sortingOptions={sortingOptions}
-              onSortingOptionsChange={onSortingOptionsChange}
-            />
-            <Columns.LastEditedBy.Header
-              sortingOptions={sortingOptions}
-              onSortingOptionsChange={onSortingOptionsChange}
-              isTrashed={isTrashed}
-            />
-            <Columns.LastEditedAt.Header
-              sortingOptions={sortingOptions}
-              onSortingOptionsChange={onSortingOptionsChange}
-              isTrashed={isTrashed}
-            />
-            {showActionMenu && <Columns.ActionMenu.Header />}
+            {visibleColumnsMap["type"] && (
+              <Columns.Type.Header
+                sortingOptions={sortingOptions}
+                onSortingOptionsChange={onSortingOptionsChange}
+              />
+            )}
+            {visibleColumnsMap["name"] && (
+              <Columns.Name.Header
+                sortingOptions={sortingOptions}
+                onSortingOptionsChange={onSortingOptionsChange}
+              />
+            )}
+            {visibleColumnsMap["lastEditedBy"] && (
+              <Columns.LastEditedBy.Header
+                sortingOptions={sortingOptions}
+                onSortingOptionsChange={onSortingOptionsChange}
+                isTrashed={isTrashed}
+              />
+            )}
+            {visibleColumnsMap["lastEditedAt"] && (
+              <Columns.LastEditedAt.Header
+                sortingOptions={sortingOptions}
+                onSortingOptionsChange={onSortingOptionsChange}
+                isTrashed={isTrashed}
+              />
+            )}
+            {visibleColumnsMap["actionMenu"] && <Columns.ActionMenu.Header />}
             <Columns.RightEdge.Header />
           </tr>
         </thead>
@@ -220,7 +231,7 @@ export const BaseItemsTable = ({
         onMove={onMove}
         onToggleSelected={onToggleSelected}
         onClick={onClick}
-        showActionMenu={showActionMenu}
+        visibleColumnsMap={visibleColumnsMap}
       />
     </Table>
   );
diff --git a/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.unit.spec.tsx b/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.unit.spec.tsx
index b2121be4d56..979d6c8e0e3 100644
--- a/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.unit.spec.tsx
+++ b/frontend/src/metabase/components/ItemsTable/BaseItemsTable/BaseItemsTable.unit.spec.tsx
@@ -3,6 +3,8 @@ import moment from "moment-timezone"; // eslint-disable-line no-restricted-impor
 import { Route } from "react-router";
 
 import { getIcon, renderWithProviders, screen } from "__support__/ui";
+import { DEFAULT_VISIBLE_COLUMNS_LIST } from "metabase/collections/components/CollectionContent";
+import { getVisibleColumnsMap } from "metabase/components/ItemsTable/utils";
 import type { ItemWithLastEditInfo } from "metabase/components/LastEditInfoLabel/LastEditInfoLabel";
 import {
   DEFAULT_DATE_STYLE,
@@ -53,6 +55,8 @@ function getCollectionItem({
   };
 }
 
+const VISIBLE_COLUMNS_MAP = getVisibleColumnsMap(DEFAULT_VISIBLE_COLUMNS_LIST);
+
 describe("BaseItemsTable", () => {
   const ITEM = getCollectionItem();
 
@@ -71,6 +75,7 @@ describe("BaseItemsTable", () => {
               sort_direction: SortDirection.Asc,
             }}
             onSortingOptionsChange={jest.fn()}
+            visibleColumnsMap={VISIBLE_COLUMNS_MAP}
             {...props}
           />
         )}
diff --git a/frontend/src/metabase/components/ItemsTable/BaseItemsTableBody/BaseItemsTableBody.tsx b/frontend/src/metabase/components/ItemsTable/BaseItemsTableBody/BaseItemsTableBody.tsx
index 80888ef8d9d..89cd90bc82f 100644
--- a/frontend/src/metabase/components/ItemsTable/BaseItemsTableBody/BaseItemsTableBody.tsx
+++ b/frontend/src/metabase/components/ItemsTable/BaseItemsTableBody/BaseItemsTableBody.tsx
@@ -26,7 +26,7 @@ export const BaseItemsTableBody = ({
   onMove,
   onToggleSelected,
   onClick,
-  showActionMenu,
+  visibleColumnsMap,
 }: Pick<
   BaseItemsTableProps,
   | "onClick"
@@ -44,7 +44,7 @@ export const BaseItemsTableBody = ({
   | "onCopy"
   | "onMove"
   | "onToggleSelected"
-  | "showActionMenu"
+  | "visibleColumnsMap"
 >) => {
   const isDndAvailable = useSelector(getIsDndAvailable);
 
@@ -77,7 +77,7 @@ export const BaseItemsTableBody = ({
             onToggleSelected={onToggleSelected}
             items={items}
             onClick={onClick}
-            showActionMenu={showActionMenu}
+            visibleColumnsMap={visibleColumnsMap}
           />
         );
       })}
diff --git a/frontend/src/metabase/components/ItemsTable/DefaultItemRenderer.tsx b/frontend/src/metabase/components/ItemsTable/DefaultItemRenderer.tsx
index 9bfea995ee0..aaa36f14ceb 100644
--- a/frontend/src/metabase/components/ItemsTable/DefaultItemRenderer.tsx
+++ b/frontend/src/metabase/components/ItemsTable/DefaultItemRenderer.tsx
@@ -19,7 +19,7 @@ export type ItemRendererProps = {
   databases?: Database[];
   bookmarks?: Bookmark[];
 } & ActionMenuProps &
-  Pick<BaseItemsTableProps, "onClick" | "showActionMenu">;
+  Pick<BaseItemsTableProps, "onClick" | "visibleColumnsMap">;
 
 export const DefaultItemRenderer = ({
   item,
@@ -35,7 +35,7 @@ export const DefaultItemRenderer = ({
   bookmarks,
   testIdPrefix = "item",
   onClick,
-  showActionMenu,
+  visibleColumnsMap,
 }: ItemRendererProps) => {
   const canSelect =
     collection?.can_write && typeof onToggleSelected === "function";
@@ -60,19 +60,27 @@ export const DefaultItemRenderer = ({
           handleSelectionToggled={handleSelectionToggled}
         />
       )}
-      <Columns.Type.Cell
-        testIdPrefix={testIdPrefix}
-        icon={icon}
-        isPinned={isPinned}
-      />
-      <Columns.Name.Cell
-        item={item}
-        testIdPrefix={testIdPrefix}
-        onClick={onClick}
-      />
-      <Columns.LastEditedBy.Cell item={item} testIdPrefix={testIdPrefix} />
-      <Columns.LastEditedAt.Cell item={item} testIdPrefix={testIdPrefix} />
-      {showActionMenu && (
+      {visibleColumnsMap["type"] && (
+        <Columns.Type.Cell
+          testIdPrefix={testIdPrefix}
+          icon={icon}
+          isPinned={isPinned}
+        />
+      )}
+      {visibleColumnsMap["name"] && (
+        <Columns.Name.Cell
+          item={item}
+          testIdPrefix={testIdPrefix}
+          onClick={onClick}
+        />
+      )}
+      {visibleColumnsMap["lastEditedBy"] && (
+        <Columns.LastEditedBy.Cell item={item} testIdPrefix={testIdPrefix} />
+      )}
+      {visibleColumnsMap["lastEditedAt"] && (
+        <Columns.LastEditedAt.Cell item={item} testIdPrefix={testIdPrefix} />
+      )}
+      {visibleColumnsMap["actionMenu"] && (
         <Columns.ActionMenu.Cell
           item={item}
           collection={collection}
diff --git a/frontend/src/metabase/components/ItemsTable/utils.ts b/frontend/src/metabase/components/ItemsTable/utils.ts
index e71f2d1a110..275facc7ad4 100644
--- a/frontend/src/metabase/components/ItemsTable/utils.ts
+++ b/frontend/src/metabase/components/ItemsTable/utils.ts
@@ -1,3 +1,7 @@
+import type {
+  CollectionContentTableColumn,
+  CollectionContentTableColumnsMap,
+} from "metabase/collections/components/CollectionContent";
 import { type BreakpointName, breakpoints } from "metabase/ui/theme";
 
 export interface ResponsiveProps {
@@ -12,3 +16,11 @@ export const getContainerQuery = (props: ResponsiveProps) =>
         breakpoints[props.hideAtContainerBreakpoint]
       }) { display: none; }`
     : "";
+
+export const getVisibleColumnsMap = (
+  visibleColumns: CollectionContentTableColumn[],
+) =>
+  visibleColumns.reduce((result, item) => {
+    result[item] = true;
+    return result;
+  }, {} as CollectionContentTableColumnsMap);
diff --git a/frontend/src/metabase/containers/dnd/ItemsDragLayer.jsx b/frontend/src/metabase/containers/dnd/ItemsDragLayer.jsx
index f8ae9262b11..3fb787d0932 100644
--- a/frontend/src/metabase/containers/dnd/ItemsDragLayer.jsx
+++ b/frontend/src/metabase/containers/dnd/ItemsDragLayer.jsx
@@ -19,6 +19,7 @@ class ItemsDragLayerInner extends Component {
       pinnedItems,
       item,
       collection,
+      visibleColumnsMap,
     } = this.props;
     if (!isDragging || !currentOffset) {
       return null;
@@ -43,6 +44,7 @@ class ItemsDragLayerInner extends Component {
           draggedItem={item.item}
           pinnedItems={pinnedItems}
           collection={collection}
+          visibleColumnsMap={visibleColumnsMap}
         />
       </div>
     );
@@ -107,7 +109,7 @@ class DraggedItems extends Component {
   };
 
   render() {
-    const { items, draggedItem } = this.props;
+    const { items, draggedItem, visibleColumnsMap } = this.props;
     const index = _.findIndex(items, draggedItem);
     const allPinned = items.every(item => this.checkIsPinned(item));
     return (
@@ -124,6 +126,7 @@ class DraggedItems extends Component {
           isInDragLayer
           style={{ width: allPinned ? 400 : undefined }}
           includeColGroup={!allPinned}
+          visibleColumnsMap={visibleColumnsMap}
         />
       </div>
     );
-- 
GitLab