diff --git a/frontend/src/metabase-types/api/database.ts b/frontend/src/metabase-types/api/database.ts
index 154abdcbeaf1caea9843107336746bf40b12e1ab..dfcfbeccbcf4a58db4bf800014de9f96d7194c79 100644
--- a/frontend/src/metabase-types/api/database.ts
+++ b/frontend/src/metabase-types/api/database.ts
@@ -9,16 +9,29 @@ export type DatabaseSettings = {
   [key: string]: any;
 };
 
+export type DatabaseFeature =
+  | "basic-aggregations"
+  | "binning"
+  | "case-sensitivity-string-filter-options"
+  | "expression-aggregations"
+  | "expressions"
+  | "foreign-keys"
+  | "native-parameters"
+  | "nested-queries"
+  | "standard-deviation-aggregations"
+  | "persist-models"
+  | "persist-models-enabled";
+
 export interface Database extends DatabaseData {
   id: DatabaseId;
   is_saved_questions: boolean;
+  features: DatabaseFeature[];
   creator_id?: number;
   created_at: string;
   timezone?: string;
   native_permissions: NativePermissions;
   initial_sync_status: InitialSyncStatus;
 
-  // appears in frontend/src/metabase/writeback/utils.ts
   settings?: DatabaseSettings | null;
 
   // Only appears in  GET /api/database/:id
diff --git a/frontend/src/metabase-types/api/mocks/database.ts b/frontend/src/metabase-types/api/mocks/database.ts
index 674b1052b9d424178e1aaa63a37b2da5c0fc739b..6a3874a56d0ed6835cc5980d0f7913d84dd7c909 100644
--- a/frontend/src/metabase-types/api/mocks/database.ts
+++ b/frontend/src/metabase-types/api/mocks/database.ts
@@ -1,4 +1,17 @@
-import { Database, DatabaseData } from "metabase-types/api";
+import { Database, DatabaseData, DatabaseFeature } from "metabase-types/api";
+
+export const COMMON_DATABASE_FEATURES: DatabaseFeature[] = [
+  "basic-aggregations",
+  "binning",
+  "case-sensitivity-string-filter-options",
+  "expression-aggregations",
+  "expressions",
+  "foreign-keys",
+  "native-parameters",
+  "nested-queries",
+  "standard-deviation-aggregations",
+  "persist-models",
+];
 
 export const createMockDatabase = (opts?: Partial<Database>): Database => ({
   ...createMockDatabaseData(opts),
@@ -10,6 +23,7 @@ export const createMockDatabase = (opts?: Partial<Database>): Database => ({
   timezone: "UTC",
   native_permissions: "write",
   initial_sync_status: "complete",
+  features: COMMON_DATABASE_FEATURES,
   ...opts,
 });
 
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.jsx b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.jsx
deleted file mode 100644
index e1d1a9b4e90fb8789bcf3d3de26a228c91640a69..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.jsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import React, { useRef } from "react";
-import PropTypes from "prop-types";
-import { t } from "ttag";
-
-import { isSyncCompleted } from "metabase/lib/syncing";
-import DeleteDatabaseModal from "metabase/admin/databases/components/DeleteDatabaseModal.jsx";
-import ActionButton from "metabase/components/ActionButton";
-import ModalWithTrigger from "metabase/components/ModalWithTrigger";
-import ConfirmContent from "metabase/components/ConfirmContent";
-import Button from "metabase/core/components/Button";
-
-import ModelCachingControl from "./ModelCachingControl";
-import { SidebarRoot } from "./Sidebar.styled";
-
-const propTypes = {
-  database: PropTypes.object.isRequired,
-  updateDatabase: PropTypes.func.isRequired,
-  deleteDatabase: PropTypes.func.isRequired,
-  syncDatabaseSchema: PropTypes.func.isRequired,
-  dismissSyncSpinner: PropTypes.func.isRequired,
-  rescanDatabaseFields: PropTypes.func.isRequired,
-  discardSavedFieldValues: PropTypes.func.isRequired,
-  isAdmin: PropTypes.bool,
-  isWritebackEnabled: PropTypes.bool,
-  isModelPersistenceEnabled: PropTypes.bool,
-};
-
-const DatabaseEditAppSidebar = ({
-  database,
-  deleteDatabase,
-  syncDatabaseSchema,
-  dismissSyncSpinner,
-  rescanDatabaseFields,
-  discardSavedFieldValues,
-  isAdmin,
-  isWritebackEnabled,
-  isModelPersistenceEnabled,
-}) => {
-  const discardSavedFieldValuesModal = useRef();
-  const deleteDatabaseModal = useRef();
-
-  return (
-    <SidebarRoot>
-      <div className="Actions bg-light rounded p3">
-        <div className="Actions-group">
-          <label className="Actions-groupLabel block text-bold">{t`Actions`}</label>
-          <ol>
-            {!isSyncCompleted(database) && (
-              <li>
-                <Button disabled borderless>{t`Syncing database…`}</Button>
-              </li>
-            )}
-            <li>
-              <ActionButton
-                actionFn={() => syncDatabaseSchema(database.id)}
-                className="Button Button--syncDbSchema"
-                normalText={t`Sync database schema now`}
-                activeText={t`Starting…`}
-                failedText={t`Failed to sync`}
-                successText={t`Sync triggered!`}
-              />
-            </li>
-            <li className="mt2">
-              <ActionButton
-                actionFn={() => rescanDatabaseFields(database.id)}
-                className="Button Button--rescanFieldValues"
-                normalText={t`Re-scan field values now`}
-                activeText={t`Starting…`}
-                failedText={t`Failed to start scan`}
-                successText={t`Scan triggered!`}
-              />
-            </li>
-            {database["initial_sync_status"] !== "complete" && (
-              <li className="mt2">
-                <ActionButton
-                  actionFn={() => dismissSyncSpinner(database.id)}
-                  className="Button Button--dismissSyncSpinner"
-                  normalText={t`Dismiss sync spinner manually`}
-                  activeText={t`Dismissing…`}
-                  failedText={t`Failed to dismiss sync spinner`}
-                  successText={t`Sync spinners dismissed!`}
-                />
-              </li>
-            )}
-            {isModelPersistenceEnabled && database.supportsPersistence() && (
-              <li className="mt2">
-                <ModelCachingControl database={database} />
-              </li>
-            )}
-          </ol>
-        </div>
-
-        <div className="Actions-group">
-          <label className="Actions-groupLabel block text-bold">{t`Danger Zone`}</label>
-          <ol>
-            {isSyncCompleted(database) && (
-              <li>
-                <ModalWithTrigger
-                  ref={discardSavedFieldValuesModal}
-                  triggerClasses="Button Button--danger Button--discardSavedFieldValues"
-                  triggerElement={t`Discard saved field values`}
-                >
-                  <ConfirmContent
-                    title={t`Discard saved field values`}
-                    onClose={() =>
-                      discardSavedFieldValuesModal.current.toggle()
-                    }
-                    onAction={() => discardSavedFieldValues(database.id)}
-                  />
-                </ModalWithTrigger>
-              </li>
-            )}
-
-            {isAdmin && (
-              <li className="mt2">
-                <ModalWithTrigger
-                  ref={deleteDatabaseModal}
-                  triggerClasses="Button Button--deleteDatabase Button--danger"
-                  triggerElement={t`Remove this database`}
-                >
-                  <DeleteDatabaseModal
-                    database={database}
-                    onClose={() => deleteDatabaseModal.current.toggle()}
-                    onDelete={() => deleteDatabase(database.id, true)}
-                  />
-                </ModalWithTrigger>
-              </li>
-            )}
-          </ol>
-        </div>
-      </div>
-    </SidebarRoot>
-  );
-};
-
-DatabaseEditAppSidebar.propTypes = propTypes;
-
-export default DatabaseEditAppSidebar;
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.styled.tsx b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.styled.tsx
index d074e1be01e3bedd6d7524a7b037038c5630638e..ebed5270a8b7f0bd9790316abe0b909c6611b135 100644
--- a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.styled.tsx
+++ b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.styled.tsx
@@ -1,4 +1,5 @@
 import styled from "@emotion/styled";
+import { color } from "metabase/lib/colors";
 import { breakpointMinSmall } from "metabase/styled-components/theme";
 
 export const SidebarRoot = styled.div`
@@ -9,3 +10,39 @@ export const SidebarRoot = styled.div`
     margin-left: 2rem;
   }
 `;
+
+const _SidebarGroup = styled.div`
+  margin-bottom: 2em;
+`;
+
+const SidebarGroupName = styled.span`
+  display: block;
+
+  font-size: 1em;
+  font-weight: bold;
+
+  margin-bottom: 1em;
+`;
+
+const SidebarGroupList = styled.ol``;
+
+const SidebarGroupListItem = styled.li<{ hasMarginTop?: boolean }>`
+  ${({ hasMarginTop = true }) => hasMarginTop && "margin-top: 1rem;"}
+`;
+
+export const SidebarGroup = Object.assign(_SidebarGroup, {
+  Name: SidebarGroupName,
+  List: SidebarGroupList,
+  ListItem: SidebarGroupListItem,
+});
+
+export const SidebarContent = styled.div`
+  padding: 1.5rem;
+
+  background-color: ${color("bg-light")};
+  border-radius: 8px;
+
+  ${SidebarGroup}:last-child {
+    margin-bottom: 0;
+  }
+`;
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.tsx b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a3a37b4417eada8af53b5255ee92a24e9e9c1f77
--- /dev/null
+++ b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.tsx
@@ -0,0 +1,169 @@
+import React, { useCallback, useRef } from "react";
+import { t } from "ttag";
+
+import Button from "metabase/core/components/Button";
+import ActionButton from "metabase/components/ActionButton";
+import ConfirmContent from "metabase/components/ConfirmContent";
+import ModalWithTrigger from "metabase/components/ModalWithTrigger";
+
+import { isSyncCompleted } from "metabase/lib/syncing";
+import DeleteDatabaseModal from "metabase/admin/databases/components/DeleteDatabaseModal.jsx";
+
+import type { DatabaseId } from "metabase-types/api";
+import type Database from "metabase-lib/metadata/Database";
+
+import ModelCachingControl from "./ModelCachingControl";
+import { SidebarRoot, SidebarContent, SidebarGroup } from "./Sidebar.styled";
+
+interface DatabaseEditAppSidebarProps {
+  database: Database;
+  isAdmin: boolean;
+  isModelPersistenceEnabled: boolean;
+  updateDatabase: (database: Database) => void;
+  syncDatabaseSchema: (databaseId: DatabaseId) => void;
+  dismissSyncSpinner: (databaseId: DatabaseId) => void;
+  rescanDatabaseFields: (databaseId: DatabaseId) => void;
+  discardSavedFieldValues: (databaseId: DatabaseId) => void;
+  deleteDatabase: (databaseId: DatabaseId, isDetailView: boolean) => void;
+}
+
+const DatabaseEditAppSidebar = ({
+  database,
+  deleteDatabase,
+  syncDatabaseSchema,
+  dismissSyncSpinner,
+  rescanDatabaseFields,
+  discardSavedFieldValues,
+  isAdmin,
+  isModelPersistenceEnabled,
+}: DatabaseEditAppSidebarProps) => {
+  const discardSavedFieldValuesModal = useRef<any>();
+  const deleteDatabaseModal = useRef<any>();
+
+  const isSynced = isSyncCompleted(database);
+  const hasModelCachingSection =
+    isModelPersistenceEnabled && database.supportsPersistence();
+
+  const handleSyncDatabaseSchema = useCallback(
+    () => syncDatabaseSchema(database.id),
+    [database, syncDatabaseSchema],
+  );
+
+  const handleReScanFieldValues = useCallback(
+    () => rescanDatabaseFields(database.id),
+    [database, rescanDatabaseFields],
+  );
+
+  const handleDismissSyncSpinner = useCallback(
+    () => dismissSyncSpinner(database.id),
+    [database, dismissSyncSpinner],
+  );
+
+  const handleDiscardSavedFieldValues = useCallback(
+    () => discardSavedFieldValues(database.id),
+    [database, discardSavedFieldValues],
+  );
+
+  const handleDeleteDatabase = useCallback(
+    () => deleteDatabase(database.id, true),
+    [database, deleteDatabase],
+  );
+
+  const handleSavedFieldsModalClose = useCallback(() => {
+    discardSavedFieldValuesModal.current.close();
+  }, []);
+
+  const handleDeleteDatabaseModalClose = useCallback(() => {
+    deleteDatabaseModal.current.close();
+  }, []);
+
+  return (
+    <SidebarRoot>
+      <SidebarContent data-testid="database-actions-panel">
+        <SidebarGroup>
+          <SidebarGroup.Name>{t`Actions`}</SidebarGroup.Name>
+          <SidebarGroup.List>
+            {!isSynced && (
+              <SidebarGroup.ListItem hasMarginTop={false}>
+                <Button disabled borderless>{t`Syncing database…`}</Button>
+              </SidebarGroup.ListItem>
+            )}
+            <SidebarGroup.ListItem hasMarginTop={false}>
+              <ActionButton
+                actionFn={handleSyncDatabaseSchema}
+                normalText={t`Sync database schema now`}
+                activeText={t`Starting…`}
+                failedText={t`Failed to sync`}
+                successText={t`Sync triggered!`}
+              />
+            </SidebarGroup.ListItem>
+            <SidebarGroup.ListItem>
+              <ActionButton
+                actionFn={handleReScanFieldValues}
+                normalText={t`Re-scan field values now`}
+                activeText={t`Starting…`}
+                failedText={t`Failed to start scan`}
+                successText={t`Scan triggered!`}
+              />
+            </SidebarGroup.ListItem>
+            {!isSynced && (
+              <SidebarGroup.ListItem>
+                <ActionButton
+                  actionFn={handleDismissSyncSpinner}
+                  normalText={t`Dismiss sync spinner manually`}
+                  activeText={t`Dismissing…`}
+                  failedText={t`Failed to dismiss sync spinner`}
+                  successText={t`Sync spinners dismissed!`}
+                />
+              </SidebarGroup.ListItem>
+            )}
+            {hasModelCachingSection && (
+              <SidebarGroup.ListItem>
+                <ModelCachingControl database={database} />
+              </SidebarGroup.ListItem>
+            )}
+          </SidebarGroup.List>
+        </SidebarGroup>
+        <SidebarGroup>
+          <SidebarGroup.Name>{t`Danger Zone`}</SidebarGroup.Name>
+          <SidebarGroup.List>
+            {isSyncCompleted(database) && (
+              <SidebarGroup.ListItem hasMarginTop={false}>
+                <ModalWithTrigger
+                  triggerElement={
+                    <Button danger>{t`Discard saved field values`}</Button>
+                  }
+                  ref={discardSavedFieldValuesModal}
+                >
+                  <ConfirmContent
+                    title={t`Discard saved field values`}
+                    onClose={handleSavedFieldsModalClose}
+                    onAction={handleDiscardSavedFieldValues}
+                  />
+                </ModalWithTrigger>
+              </SidebarGroup.ListItem>
+            )}
+            {isAdmin && (
+              <SidebarGroup.ListItem>
+                <ModalWithTrigger
+                  triggerElement={
+                    <Button danger>{t`Remove this database`}</Button>
+                  }
+                  ref={deleteDatabaseModal}
+                >
+                  <DeleteDatabaseModal
+                    database={database}
+                    onClose={handleDeleteDatabaseModalClose}
+                    onDelete={handleDeleteDatabase}
+                  />
+                </ModalWithTrigger>
+              </SidebarGroup.ListItem>
+            )}
+          </SidebarGroup.List>
+        </SidebarGroup>
+      </SidebarContent>
+    </SidebarRoot>
+  );
+};
+
+export default DatabaseEditAppSidebar;
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.unit.spec.js b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.unit.spec.js
deleted file mode 100644
index 8f2affd55cab7bb2bb8783a4be7741aef97a4e23..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.unit.spec.js
+++ /dev/null
@@ -1,161 +0,0 @@
-import React from "react";
-import { fireEvent, render, screen } from "@testing-library/react";
-import userEvent from "@testing-library/user-event";
-
-import Sidebar from "./Sidebar";
-
-it("syncs database schema", () => {
-  const databaseId = 1;
-  const database = {
-    id: databaseId,
-    initial_sync_status: "complete",
-    supportsPersistence: () => true,
-    isPersisted: () => false,
-  };
-  const syncDatabaseSchema = jest.fn();
-
-  render(
-    <Sidebar database={database} syncDatabaseSchema={syncDatabaseSchema} />,
-  );
-
-  const syncButton = screen.getByText("Sync database schema now");
-
-  fireEvent.click(syncButton);
-
-  expect(syncDatabaseSchema).toHaveBeenCalledWith(databaseId);
-});
-
-it("rescans database field values", () => {
-  const databaseId = 1;
-  const database = {
-    id: databaseId,
-    initial_sync_status: "complete",
-    supportsPersistence: () => true,
-    isPersisted: () => false,
-  };
-  const rescanDatabaseFields = jest.fn();
-
-  render(
-    <Sidebar database={database} rescanDatabaseFields={rescanDatabaseFields} />,
-  );
-
-  const rescanButton = screen.getByText("Re-scan field values now");
-
-  fireEvent.click(rescanButton);
-
-  expect(rescanDatabaseFields).toHaveBeenCalledWith(databaseId);
-});
-
-it("can cancel sync and just forgets about initial sync (#20863)", () => {
-  const databaseId = 1;
-  const database = {
-    id: databaseId,
-    initial_sync_status: "incomplete",
-    supportsPersistence: () => true,
-    isPersisted: () => false,
-  };
-  const dismissSyncSpinner = jest.fn();
-
-  render(
-    <Sidebar database={database} dismissSyncSpinner={dismissSyncSpinner} />,
-  );
-
-  const dismissButton = screen.getByText("Dismiss sync spinner manually");
-  fireEvent.click(dismissButton);
-  expect(dismissSyncSpinner).toHaveBeenCalledWith(databaseId);
-});
-
-it("discards saved field values", () => {
-  const databaseId = 1;
-  const database = {
-    id: databaseId,
-    initial_sync_status: "complete",
-    supportsPersistence: () => true,
-    isPersisted: () => false,
-  };
-  const discardSavedFieldValues = jest.fn();
-
-  render(
-    <Sidebar
-      database={database}
-      discardSavedFieldValues={discardSavedFieldValues}
-    />,
-  );
-
-  const discardButton = screen.getByText("Discard saved field values");
-
-  fireEvent.click(discardButton);
-
-  expect(screen.getAllByText("Discard saved field values").length).toBe(2);
-
-  const cancelButton = screen.getByText("Cancel");
-
-  fireEvent.click(cancelButton);
-
-  fireEvent.click(discardButton);
-
-  const yesButton = screen.getByText("Yes");
-
-  fireEvent.click(yesButton);
-
-  expect(discardSavedFieldValues).toHaveBeenCalledWith(databaseId);
-});
-
-it("removes database", () => {
-  const databaseId = 1;
-  const name = "DB Name";
-  const database = {
-    id: databaseId,
-    name,
-    supportsPersistence: () => true,
-    isPersisted: () => false,
-  };
-  const deleteDatabase = jest.fn();
-
-  render(
-    <Sidebar database={database} deleteDatabase={deleteDatabase} isAdmin />,
-  );
-
-  const removeDBButton = screen.getByText("Remove this database");
-
-  fireEvent.click(removeDBButton);
-
-  screen.getByText(`Delete the ${name} database?`);
-
-  const cancelButton = screen.getByText("Cancel");
-
-  fireEvent.click(cancelButton);
-
-  fireEvent.click(removeDBButton);
-
-  const input = screen.getByRole("textbox");
-
-  userEvent.type(input, name);
-
-  const deleteButton = screen.getByText("Delete");
-
-  fireEvent.click(deleteButton);
-
-  expect(deleteDatabase).toHaveBeenCalled();
-});
-
-it("does not allow to remove databases for non-admins", () => {
-  const database = { id: 1, name: "DB Name" };
-  render(<Sidebar database={database} deleteDatabase={jest.fn()} />);
-  expect(screen.queryByText("Remove this database")).toBeNull();
-});
-
-it("shows loading indicator when a sync is in progress", () => {
-  const databaseId = 1;
-  const database = {
-    id: databaseId,
-    initial_sync_status: "incomplete",
-    supportsPersistence: () => true,
-    isPersisted: () => false,
-  };
-
-  render(<Sidebar database={database} />);
-
-  const statusButton = screen.getByText("Syncing database…");
-  expect(statusButton).toBeInTheDocument();
-});
diff --git a/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.unit.spec.tsx b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..916f14c97ae338f0ed2585a0681534c609c36b37
--- /dev/null
+++ b/frontend/src/metabase/admin/databases/components/DatabaseEditApp/Sidebar/Sidebar.unit.spec.tsx
@@ -0,0 +1,251 @@
+import React from "react";
+import _ from "underscore";
+import {
+  getByRole,
+  getByText,
+  render,
+  screen,
+  waitForElementToBeRemoved,
+} from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+
+import MetabaseSettings from "metabase/lib/settings";
+import Utils from "metabase/lib/utils";
+
+import type { InitialSyncStatus } from "metabase-types/api";
+
+import {
+  createMockDatabase,
+  COMMON_DATABASE_FEATURES,
+} from "metabase-types/api/mocks";
+import Database from "metabase-lib/metadata/Database";
+
+import Sidebar from "./Sidebar";
+
+const NOT_SYNCED_DB_STATUSES: InitialSyncStatus[] = ["aborted", "incomplete"];
+
+function getModal() {
+  return document.querySelector(".Modal") as HTMLElement;
+}
+
+function setup({
+  database = createMockDatabase(),
+  isAdmin = true,
+  isModelPersistenceEnabled = false,
+} = {}) {
+  // Using mockResolvedValue since `ActionButton` component
+  // the Sidebar is using is expecting these callbacks to be async
+  const updateDatabase = jest.fn().mockResolvedValue({});
+  const syncDatabaseSchema = jest.fn().mockResolvedValue({});
+  const rescanDatabaseFields = jest.fn().mockResolvedValue({});
+  const discardSavedFieldValues = jest.fn().mockResolvedValue({});
+  const dismissSyncSpinner = jest.fn().mockResolvedValue({});
+  const deleteDatabase = jest.fn().mockResolvedValue({});
+
+  const utils = render(
+    <Sidebar
+      database={new Database(database)}
+      isAdmin={isAdmin}
+      isModelPersistenceEnabled={isModelPersistenceEnabled}
+      updateDatabase={updateDatabase}
+      syncDatabaseSchema={syncDatabaseSchema}
+      rescanDatabaseFields={rescanDatabaseFields}
+      discardSavedFieldValues={discardSavedFieldValues}
+      dismissSyncSpinner={dismissSyncSpinner}
+      deleteDatabase={deleteDatabase}
+    />,
+  );
+
+  return {
+    ...utils,
+    database,
+    updateDatabase,
+    syncDatabaseSchema,
+    rescanDatabaseFields,
+    discardSavedFieldValues,
+    dismissSyncSpinner,
+    deleteDatabase,
+  };
+}
+
+function mockMetabaseSettings() {
+  const original = MetabaseSettings.get.bind(MetabaseSettings);
+  const spy = jest.spyOn(MetabaseSettings, "get");
+  spy.mockImplementation(key => {
+    if (key === "site-uuid") {
+      return Utils.uuid();
+    }
+    return original(key);
+  });
+}
+
+describe("DatabaseEditApp/Sidebar", () => {
+  beforeAll(() => {
+    mockMetabaseSettings();
+  });
+
+  it("syncs database schema", () => {
+    const { database, syncDatabaseSchema } = setup();
+    userEvent.click(screen.getByText(/Sync database schema now/i));
+    expect(syncDatabaseSchema).toHaveBeenCalledWith(database.id);
+  });
+
+  it("re-scans database field values", () => {
+    const { database, rescanDatabaseFields } = setup();
+    userEvent.click(screen.getByText(/Re-scan field values now/i));
+    expect(rescanDatabaseFields).toHaveBeenCalledWith(database.id);
+  });
+
+  describe("sync indicator", () => {
+    it("isn't shown for a fully synced database", () => {
+      setup({
+        database: createMockDatabase({ initial_sync_status: "complete" }),
+      });
+
+      expect(screen.queryByText(/Syncing database…/i)).not.toBeInTheDocument();
+      expect(
+        screen.queryByText(/Dismiss sync spinner manually/i),
+      ).not.toBeInTheDocument();
+    });
+
+    NOT_SYNCED_DB_STATUSES.forEach(initial_sync_status => {
+      it(`is shown for a database with "${initial_sync_status}" sync status`, () => {
+        setup({ database: createMockDatabase({ initial_sync_status }) });
+
+        expect(screen.getByText(/Syncing database…/i)).toBeInTheDocument();
+        expect(
+          screen.getByText(/Dismiss sync spinner manually/i),
+        ).toBeInTheDocument();
+      });
+
+      it(`can be dismissed for a database with "${initial_sync_status}" sync status (#20863)`, () => {
+        const database = createMockDatabase({ initial_sync_status });
+        const { dismissSyncSpinner } = setup({ database });
+
+        userEvent.click(screen.getByText(/Dismiss sync spinner manually/i));
+
+        expect(dismissSyncSpinner).toHaveBeenCalledWith(database.id);
+      });
+    });
+  });
+
+  describe("discarding field values", () => {
+    it("discards field values", () => {
+      const { database, discardSavedFieldValues } = setup();
+
+      userEvent.click(screen.getByText(/Discard saved field values/i));
+      userEvent.click(getByRole(getModal(), "button", { name: "Yes" }));
+
+      expect(discardSavedFieldValues).toHaveBeenCalledWith(database.id);
+    });
+
+    it("allows to cancel confirmation modal", async () => {
+      const { discardSavedFieldValues } = setup();
+
+      userEvent.click(screen.getByText(/Discard saved field values/i));
+      userEvent.click(getByRole(getModal(), "button", { name: "Cancel" }));
+      await waitForElementToBeRemoved(() => getModal());
+
+      expect(getModal()).not.toBeInTheDocument();
+      expect(discardSavedFieldValues).not.toBeCalled();
+    });
+
+    NOT_SYNCED_DB_STATUSES.forEach(initial_sync_status => {
+      it(`is hidden for databases with "${initial_sync_status}" sync status`, () => {
+        setup({
+          database: createMockDatabase({ initial_sync_status }),
+        });
+
+        expect(
+          screen.queryByText(/Discard saved field values/i),
+        ).not.toBeInTheDocument();
+      });
+    });
+  });
+
+  describe("model caching control", () => {
+    it("isn't shown if model caching is turned off globally", () => {
+      setup({ isModelPersistenceEnabled: false });
+
+      expect(
+        screen.queryByText(/Turn model caching on/i),
+      ).not.toBeInTheDocument();
+      expect(
+        screen.queryByText(/Turn model caching off/i),
+      ).not.toBeInTheDocument();
+    });
+
+    it("isn't shown if database doesn't support model caching", () => {
+      setup({
+        isModelPersistenceEnabled: true,
+        database: createMockDatabase({
+          features: _.without(COMMON_DATABASE_FEATURES, "persist-models"),
+        }),
+      });
+
+      expect(
+        screen.queryByText(/Turn model caching on/i),
+      ).not.toBeInTheDocument();
+      expect(
+        screen.queryByText(/Turn model caching off/i),
+      ).not.toBeInTheDocument();
+    });
+
+    it("offers to enable caching when it's enabled on the instance and supported by a database", () => {
+      setup({ isModelPersistenceEnabled: true });
+      expect(screen.getByText(/Turn model caching on/i)).toBeInTheDocument();
+      expect(
+        screen.queryByText(/Turn model caching off/i),
+      ).not.toBeInTheDocument();
+    });
+
+    it("offers to disable caching when it's enabled for a database", () => {
+      setup({
+        isModelPersistenceEnabled: true,
+        database: createMockDatabase({
+          features: [...COMMON_DATABASE_FEATURES, "persist-models-enabled"],
+        }),
+      });
+      expect(screen.getByText(/Turn model caching off/i)).toBeInTheDocument();
+      expect(
+        screen.queryByText(/Turn model caching on/i),
+      ).not.toBeInTheDocument();
+    });
+  });
+
+  describe("database removal", () => {
+    it("isn't shown for non-admins", () => {
+      setup({ isAdmin: false });
+      expect(
+        screen.queryByText(/Remove this database/i),
+      ).not.toBeInTheDocument();
+    });
+
+    it("removes database", async () => {
+      const { database, deleteDatabase } = setup({ isAdmin: true });
+      userEvent.click(screen.getByText(/Remove this database/i));
+      const modal = getModal();
+
+      // Fill in database name to confirm deletion
+      userEvent.type(getByRole(modal, "textbox"), database.name);
+      userEvent.click(getByRole(modal, "button", { name: "Delete" }));
+      await waitForElementToBeRemoved(() => getModal());
+
+      expect(getModal()).not.toBeInTheDocument();
+      expect(deleteDatabase).toHaveBeenCalled();
+    });
+
+    it("allows to dismiss confirmation modal", async () => {
+      const { database, deleteDatabase } = setup({ isAdmin: true });
+      userEvent.click(screen.getByText(/Remove this database/i));
+      const modal = getModal();
+
+      getByText(modal, `Delete the ${database.name} database?`);
+      userEvent.click(getByRole(modal, "button", { name: "Cancel" }));
+      await waitForElementToBeRemoved(() => getModal());
+
+      expect(getModal()).not.toBeInTheDocument();
+      expect(deleteDatabase).not.toBeCalled();
+    });
+  });
+});
diff --git a/frontend/src/metabase/css/admin.css b/frontend/src/metabase/css/admin.css
index 14bf1e9a9240eca490aef1d1a289d8f5b4a8d730..da271ff9cbd5e66285866e914166ac9b9a97fb5f 100644
--- a/frontend/src/metabase/css/admin.css
+++ b/frontend/src/metabase/css/admin.css
@@ -12,19 +12,6 @@
   transition: background 0.2s linear;
 }
 
-.Actions-group {
-  margin-bottom: 2em;
-}
-
-.Actions-group:last-child {
-  margin-bottom: 0;
-}
-
-.Actions-groupLabel {
-  font-size: 1em;
-  margin-bottom: 1em;
-}
-
 .ContentTable {
   width: 100%;
   border-collapse: collapse;
diff --git a/frontend/test/metabase/scenarios/permissions/database-details-permissions.cy.spec.js b/frontend/test/metabase/scenarios/permissions/database-details-permissions.cy.spec.js
index 7e16b63ae9bff6f3135a973d5e35153aee26ca99..3537c6441aa10d1809a57a5341cb91606ab89443 100644
--- a/frontend/test/metabase/scenarios/permissions/database-details-permissions.cy.spec.js
+++ b/frontend/test/metabase/scenarios/permissions/database-details-permissions.cy.spec.js
@@ -56,7 +56,7 @@ describeEE(
 
       cy.findByText("Sample Database").click();
 
-      cy.get(".Actions")
+      cy.findByTestId("database-actions-panel")
         .should("contain", "Sync database schema now")
         .and("contain", "Re-scan field values now")
         .and("contain", "Discard saved field values")