diff --git a/frontend/src/metabase-lib/metadata/Metric.ts b/frontend/src/metabase-lib/metadata/Metric.ts
index 0212cf0af58c9ee4eb2433feb4a54a34bb97ae7a..77f5bfabdd4b40a1f326088b2d92dd9d27a08a80 100644
--- a/frontend/src/metabase-lib/metadata/Metric.ts
+++ b/frontend/src/metabase-lib/metadata/Metric.ts
@@ -1,38 +1,36 @@
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-nocheck
+import { Aggregation, NormalizedMetric } from "metabase-types/api";
 import Filter from "metabase-lib/queries/structured/Filter";
-import Base from "./Base";
 import type Metadata from "./Metadata";
 import type Table from "./Table";
-/**
- * @typedef { import("./Metadata").Aggregation } Aggregation
- */
 
-/**
- * Wrapper class for a metric. Belongs to a {@link Database} and possibly a {@link Table}
- */
+interface Metric extends Omit<NormalizedMetric, "table"> {
+  table?: Table;
+  metadata?: Metadata;
+}
 
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default class Metric extends Base {
-  name: string;
-  table_id: Table["id"];
-  table: Table;
-  metadata: Metadata;
+class Metric {
+  private readonly _plainObject: NormalizedMetric;
+
+  constructor(metric: NormalizedMetric) {
+    this._plainObject = metric;
+    Object.assign(this, metric);
+  }
+
+  getPlainObject() {
+    return this._plainObject;
+  }
 
   displayName() {
     return this.name;
   }
 
-  /**
-   * @returns {Aggregation}
-   */
-  aggregationClause() {
+  aggregationClause(): Aggregation {
     return ["metric", this.id];
   }
 
   /** Underlying query for this metric */
   definitionQuery() {
-    return this.definition
+    return this.table && this.definition
       ? this.table.query().setQuery(this.definition)
       : null;
   }
@@ -66,26 +64,7 @@ export default class Metric extends Base {
   isActive() {
     return !this.archived;
   }
-
-  /**
-   * @private
-   * @param {string} name
-   * @param {string} description
-   * @param {Database} database
-   * @param {Table} table
-   * @param {number} id
-   * @param {StructuredQuery} definition
-   * @param {boolean} archived
-   */
-
-  /* istanbul ignore next */
-  _constructor(name, description, database, table, id, definition, archived) {
-    this.name = name;
-    this.description = description;
-    this.database = database;
-    this.table = table;
-    this.id = id;
-    this.definition = definition;
-    this.archived = archived;
-  }
 }
+
+// eslint-disable-next-line import/no-default-export -- deprecated usage
+export default Metric;
diff --git a/frontend/src/metabase-lib/metadata/Schema.ts b/frontend/src/metabase-lib/metadata/Schema.ts
index 80704ceaec65a6c1d5c444d7d853836559bd273a..2a18c70beddb31da6f045d8eec7711885fb45781 100644
--- a/frontend/src/metabase-lib/metadata/Schema.ts
+++ b/frontend/src/metabase-lib/metadata/Schema.ts
@@ -4,27 +4,22 @@ import type Metadata from "./Metadata";
 import type Database from "./Database";
 import type Table from "./Table";
 
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default class Schema {
-  private readonly schema: NormalizedSchema;
-  metadata?: Metadata;
+interface Schema extends Omit<NormalizedSchema, "database" | "tables"> {
   database?: Database;
-  tables: Table[] = [];
-
-  constructor(schema: NormalizedSchema) {
-    this.schema = schema;
-  }
+  tables?: Table[];
+  metadata?: Metadata;
+}
 
-  get id() {
-    return this.schema.id;
-  }
+class Schema {
+  private readonly _plainObject: NormalizedSchema;
 
-  get name() {
-    return this.schema.name;
+  constructor(schema: NormalizedSchema) {
+    this._plainObject = schema;
+    Object.assign(this, schema);
   }
 
   getPlainObject() {
-    return this.schema;
+    return this._plainObject;
   }
 
   displayName() {
@@ -32,6 +27,9 @@ export default class Schema {
   }
 
   getTables() {
-    return this.tables;
+    return this.tables ?? [];
   }
 }
+
+// eslint-disable-next-line import/no-default-export -- deprecated usage
+export default Schema;
diff --git a/frontend/src/metabase-lib/metadata/Segment.ts b/frontend/src/metabase-lib/metadata/Segment.ts
index 8f26365f9753928a79ffcc89948fc20b93ec8d3a..7ba1970b76f96088fcc9f7ceab89af9d21632f2d 100644
--- a/frontend/src/metabase-lib/metadata/Segment.ts
+++ b/frontend/src/metabase-lib/metadata/Segment.ts
@@ -1,56 +1,36 @@
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-nocheck
-import Base from "./Base";
+import { Filter, NormalizedSegment } from "metabase-types/api";
 import type Metadata from "./Metadata";
 import type Table from "./Table";
-/**
- * @typedef { import("./Metadata").FilterClause } FilterClause
- */
 
-/**
- * Wrapper class for a segment. Belongs to a {@link Database} and possibly a {@link Table}
- */
+interface Segment extends Omit<NormalizedSegment, "table"> {
+  table?: Table;
+  metadata?: Metadata;
+}
 
-// eslint-disable-next-line import/no-default-export -- deprecated usage
-export default class Segment extends Base {
-  id: number;
-  name: string;
-  table_id: Table["id"];
-  table: Table;
-  metadata: Metadata;
+class Segment {
+  private readonly _plainObject: NormalizedSegment;
+
+  constructor(segment: NormalizedSegment) {
+    this._plainObject = segment;
+    Object.assign(this, segment);
+  }
+
+  getPlainObject() {
+    return this._plainObject;
+  }
 
   displayName() {
     return this.name;
   }
 
-  /**
-   * @returns {FilterClause}
-   */
-  filterClause() {
+  filterClause(): Filter {
     return ["segment", this.id];
   }
 
   isActive() {
     return !this.archived;
   }
-
-  /**
-   * @private
-   * @param {string} name
-   * @param {string} description
-   * @param {Database} database
-   * @param {Table} table
-   * @param {number} id
-   * @param {boolean} archived
-   */
-
-  /* istanbul ignore next */
-  _constructor(name, description, database, table, id, archived) {
-    this.name = name;
-    this.description = description;
-    this.database = database;
-    this.table = table;
-    this.id = id;
-    this.archived = archived;
-  }
 }
+
+// eslint-disable-next-line import/no-default-export -- deprecated usage
+export default Segment;
diff --git a/frontend/src/metabase-lib/metadata/Segment.unit.spec.ts b/frontend/src/metabase-lib/metadata/Segment.unit.spec.ts
index 0dd0163a0fd2f2b7beb8b2e693a011240cc14f24..56332969d54897f4937ffac04ec6b79b8a700af2 100644
--- a/frontend/src/metabase-lib/metadata/Segment.unit.spec.ts
+++ b/frontend/src/metabase-lib/metadata/Segment.unit.spec.ts
@@ -1,20 +1,12 @@
 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 // @ts-nocheck
 import Segment from "./Segment";
-import Base from "./Base";
+
 describe("Segment", () => {
   describe("instantiation", () => {
     it("should create an instance of Segment", () => {
       expect(new Segment()).toBeInstanceOf(Segment);
     });
-    it("should add `object` props to the instance (because it extends Base)", () => {
-      expect(new Segment()).toBeInstanceOf(Base);
-      expect(
-        new Segment({
-          foo: "bar",
-        }),
-      ).toHaveProperty("foo", "bar");
-    });
   });
   describe("displayName", () => {
     it("should return the `name` property found on the instance", () => {
diff --git a/frontend/src/metabase-types/api/metric.ts b/frontend/src/metabase-types/api/metric.ts
index 6912b8b01f955c4765b5ae15e43f72e49c06e792..8bf9b99a8c245738f34005cdccabf787d2efda7e 100644
--- a/frontend/src/metabase-types/api/metric.ts
+++ b/frontend/src/metabase-types/api/metric.ts
@@ -1,7 +1,7 @@
 import { StructuredQuery } from "./query";
 import { TableId } from "./table";
 
-export type MetricId = number;
+export type MetricId = number | string;
 
 export interface Metric {
   id: MetricId;
diff --git a/frontend/src/metabase-types/api/mocks/segment.ts b/frontend/src/metabase-types/api/mocks/segment.ts
index 4ddf87391e975dd80c547b40d2fb860b3685c45b..656b72c66996eaf0ff912450b474e9194e38839a 100644
--- a/frontend/src/metabase-types/api/mocks/segment.ts
+++ b/frontend/src/metabase-types/api/mocks/segment.ts
@@ -8,5 +8,6 @@ export const createMockSegment = (opts?: Partial<Segment>): Segment => ({
   table_id: 1,
   archived: false,
   definition: createMockStructuredQuery(),
+  definition_description: "",
   ...opts,
 });
diff --git a/frontend/src/metabase-types/api/segment.ts b/frontend/src/metabase-types/api/segment.ts
index e27252b15e6be5125da4ce910c5dd145e7be2f0d..809f8a0cb393dd22ce4b5a44b45efdd0ebe21274 100644
--- a/frontend/src/metabase-types/api/segment.ts
+++ b/frontend/src/metabase-types/api/segment.ts
@@ -10,5 +10,6 @@ export interface Segment {
   table_id: TableId;
   archived: boolean;
   definition: StructuredQuery;
+  definition_description: string;
   revision_message?: string;
 }
diff --git a/frontend/src/metabase/admin/datamodel/components/MetricItem.jsx b/frontend/src/metabase/admin/datamodel/components/MetricItem.jsx
index c228f1300af3e5341236c6e7862847deae46d880..b7141f404e09b28244f59aa2223ed7486dcd1af5 100644
--- a/frontend/src/metabase/admin/datamodel/components/MetricItem.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/MetricItem.jsx
@@ -17,7 +17,7 @@ export default class MetricItem extends Component {
       <tr>
         <td className="px1 py1 text-wrap">
           <span className="flex align-center">
-            <Icon {...metric.getIcon()} size={12} className="mr1 text-medium" />
+            <Icon name="sum" size={12} className="mr1 text-medium" />
             <span className="text-dark text-bold">{metric.name}</span>
           </span>
         </td>
diff --git a/frontend/src/metabase/admin/datamodel/components/SegmentItem.jsx b/frontend/src/metabase/admin/datamodel/components/SegmentItem.jsx
index a92ddb35d18709467d304d561cac01953fd0907c..51000fe43bff1c85178b901cac64e8f096f09d64 100644
--- a/frontend/src/metabase/admin/datamodel/components/SegmentItem.jsx
+++ b/frontend/src/metabase/admin/datamodel/components/SegmentItem.jsx
@@ -17,11 +17,7 @@ export default class SegmentItem extends Component {
       <tr className="mt1 mb3">
         <td className="px1 py1 text-wrap">
           <span className="flex align-center">
-            <Icon
-              {...segment.getIcon()}
-              size={12}
-              className="mr1 text-medium"
-            />
+            <Icon name="segment" size={12} className="mr1 text-medium" />
             <span className="text-dark text-bold">{segment.name}</span>
           </span>
         </td>
diff --git a/frontend/src/metabase/admin/datamodel/containers/MetricListApp.jsx b/frontend/src/metabase/admin/datamodel/containers/MetricListApp.jsx
index 180a598bcc0eb9b3f4ce422a4904142ea590bf14..e9f170d9fb2806634db07d7cc4a13cb29661e001 100644
--- a/frontend/src/metabase/admin/datamodel/containers/MetricListApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/MetricListApp.jsx
@@ -1,5 +1,6 @@
 /* eslint-disable react/prop-types */
 import React from "react";
+import { connect } from "react-redux";
 import { t } from "ttag";
 import _ from "underscore";
 
@@ -12,7 +13,7 @@ import Link from "metabase/core/components/Link";
 
 class MetricListAppInner extends React.Component {
   render() {
-    const { metrics, tableSelector } = this.props;
+    const { metrics, tableSelector, setArchived } = this.props;
 
     return (
       <div className="px3 pb2">
@@ -34,7 +35,7 @@ class MetricListAppInner extends React.Component {
             {metrics.map(metric => (
               <MetricItem
                 key={metric.id}
-                onRetire={() => metric.setArchived(true)}
+                onRetire={() => setArchived(metric, true)}
                 metric={metric}
               />
             ))}
@@ -51,8 +52,9 @@ class MetricListAppInner extends React.Component {
 }
 
 const MetricListApp = _.compose(
-  Metrics.loadList({ wrapped: true }),
+  Metrics.loadList(),
   FilteredToUrlTable("metrics"),
+  connect(null, { setArchived: Metrics.actions.setArchived }),
 )(MetricListAppInner);
 
 export default MetricListApp;
diff --git a/frontend/src/metabase/admin/datamodel/containers/SegmentListApp.jsx b/frontend/src/metabase/admin/datamodel/containers/SegmentListApp.jsx
index 70a5521f8152965bcd8a48fdb5911abd18837045..58a1ed522e401716b127eb63b6a9f5937c7700ec 100644
--- a/frontend/src/metabase/admin/datamodel/containers/SegmentListApp.jsx
+++ b/frontend/src/metabase/admin/datamodel/containers/SegmentListApp.jsx
@@ -1,9 +1,10 @@
 /* eslint-disable react/prop-types */
 import React from "react";
+import { connect } from "react-redux";
 import { t } from "ttag";
 import _ from "underscore";
 
-import Segment from "metabase/entities/segments";
+import Segments from "metabase/entities/segments";
 import SegmentItem from "metabase/admin/datamodel/components/SegmentItem";
 import FilteredToUrlTable from "metabase/admin/datamodel/hoc/FilteredToUrlTable";
 
@@ -12,7 +13,7 @@ import Link from "metabase/core/components/Link";
 
 class SegmentListAppInner extends React.Component {
   render() {
-    const { segments, tableSelector } = this.props;
+    const { segments, tableSelector, setArchived } = this.props;
 
     return (
       <div className="px3 pb2">
@@ -34,7 +35,7 @@ class SegmentListAppInner extends React.Component {
             {segments.map(segment => (
               <SegmentItem
                 key={segment.id}
-                onRetire={() => segment.setArchived(true)}
+                onRetire={() => setArchived(segment, true)}
                 segment={segment}
               />
             ))}
@@ -51,8 +52,9 @@ class SegmentListAppInner extends React.Component {
 }
 
 const SegmentListApp = _.compose(
-  Segment.loadList({ wrapped: true }),
+  Segments.loadList(),
   FilteredToUrlTable("segments"),
+  connect(null, { setArchived: Segments.actions.setArchived }),
 )(SegmentListAppInner);
 
 export default SegmentListApp;
diff --git a/frontend/src/metabase/admin/permissions/utils/graph/data-permissions.ts b/frontend/src/metabase/admin/permissions/utils/graph/data-permissions.ts
index d53f002d3ae7eec954c38000cd03c383ed73e4da..e246ea03d8c07c1fd4cfdf21143e624c1f254a9c 100644
--- a/frontend/src/metabase/admin/permissions/utils/graph/data-permissions.ts
+++ b/frontend/src/metabase/admin/permissions/utils/graph/data-permissions.ts
@@ -353,7 +353,7 @@ export function updateTablesPermission(
   downgradeNative?: boolean,
 ) {
   const schema = database.schema(schemaName);
-  const tableIds = schema?.tables.map((t: Table) => t.id);
+  const tableIds = schema?.getTables().map((t: Table) => t.id);
 
   permissions = updateSchemasPermission(
     permissions,
diff --git a/frontend/src/metabase/common/hooks/index.ts b/frontend/src/metabase/common/hooks/index.ts
index 975276fc031b56e5556eb763bc077607d923bca2..4875b686164a9e1471096fe653f434fc1d582dc5 100644
--- a/frontend/src/metabase/common/hooks/index.ts
+++ b/frontend/src/metabase/common/hooks/index.ts
@@ -1,6 +1,10 @@
 export * from "./use-database-id-field-list-query";
 export * from "./use-database-list-query";
 export * from "./use-database-query";
+export * from "./use-metric-list-query";
+export * from "./use-metric-query";
 export * from "./use-schema-list-query";
+export * from "./use-segment-list-query";
+export * from "./use-segment-query";
 export * from "./use-table-list-query";
 export * from "./use-table-query";
diff --git a/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.ts b/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.ts
index 43db832bd06867072f5e429ba3566ba1fab8c433..0f6c35a27f77981637b83bfea8dd7dff79a5bb0f 100644
--- a/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.ts
+++ b/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.ts
@@ -11,10 +11,11 @@ export const useDatabaseIdFieldListQuery = (
   props: UseEntityQueryProps<DatabaseId, DatabaseIdFieldListQuery>,
 ): UseEntityQueryResult<Field[]> => {
   return useEntityQuery(props, {
-    fetch: Databases.actions.fetchIdfields,
-    getObject: Databases.selectors.getIdfields,
+    fetch: Databases.actions.fetchIdFields,
+    getObject: state =>
+      Databases.selectors.getIdFields(state, { databaseId: props.id }),
     getLoading: Databases.selectors.getLoading,
     getError: Databases.selectors.getError,
-    requestType: "idfields",
+    requestType: "idFields",
   });
 };
diff --git a/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.unit.spec.tsx b/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fba4e56191e0d8e7bfb3720180d704ba9f540346
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-database-id-field-list-query/use-database-id-field-list-query.unit.spec.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import { createSampleDatabase } from "metabase-types/api/mocks/presets";
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
+import {
+  renderWithProviders,
+  screen,
+  waitForElementToBeRemoved,
+} from "__support__/ui";
+import { useDatabaseIdFieldListQuery } from "./use-database-id-field-list-query";
+
+const TEST_DB = createSampleDatabase();
+
+const TestComponent = () => {
+  const {
+    data = [],
+    isLoading,
+    error,
+  } = useDatabaseIdFieldListQuery({ id: TEST_DB.id });
+
+  if (isLoading || error) {
+    return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
+  }
+
+  return (
+    <div>
+      {data.map(field => (
+        <div key={field.getId()}>
+          {field.displayName({ includeTable: true })}
+        </div>
+      ))}
+    </div>
+  );
+};
+
+const setup = () => {
+  setupDatabasesEndpoints([TEST_DB]);
+  renderWithProviders(<TestComponent />);
+};
+
+describe("useDatabaseIdFieldListQuery", () => {
+  it("should be initially loading", () => {
+    setup();
+    expect(screen.getByText("Loading...")).toBeInTheDocument();
+  });
+
+  it("should show data from the response", async () => {
+    setup();
+    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
+    expect(screen.getByText("Orders → ID")).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts b/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts
index d0b41e9c0a5af982239a0f2f3fd48f0ebf7f4135..0db9c12bcc61cbe13cd9788e5cc3be7b5b542b98 100644
--- a/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts
+++ b/frontend/src/metabase/common/hooks/use-entity-list-query/use-entity-list-query.ts
@@ -7,11 +7,11 @@ export interface EntityFetchOptions {
   reload?: boolean;
 }
 
-export interface EntityQueryOptions<TQuery> {
+export interface EntityQueryOptions<TQuery = never> {
   entityQuery?: TQuery;
 }
 
-export interface UseEntityListOwnProps<TItem, TQuery> {
+export interface UseEntityListOwnProps<TItem, TQuery = never> {
   fetchList: (query?: TQuery, options?: EntityFetchOptions) => Action;
   getList: (
     state: State,
@@ -21,7 +21,7 @@ export interface UseEntityListOwnProps<TItem, TQuery> {
   getError: (state: State, options: EntityQueryOptions<TQuery>) => unknown;
 }
 
-export interface UseEntityListQueryProps<TQuery> {
+export interface UseEntityListQueryProps<TQuery = never> {
   query?: TQuery;
   reload?: boolean;
   enabled?: boolean;
@@ -33,7 +33,7 @@ export interface UseEntityListQueryResult<TItem> {
   error: unknown;
 }
 
-export const useEntityListQuery = <TItem, TQuery>(
+export const useEntityListQuery = <TItem, TQuery = never>(
   {
     query: entityQuery,
     reload = false,
diff --git a/frontend/src/metabase/common/hooks/use-entity-query/use-entity-query.ts b/frontend/src/metabase/common/hooks/use-entity-query/use-entity-query.ts
index 11d8128a734d589ea3a09c6c02518c353f0de47a..d4e3f1914bb51b2b7c2f6d0c19e444cac52a6714 100644
--- a/frontend/src/metabase/common/hooks/use-entity-query/use-entity-query.ts
+++ b/frontend/src/metabase/common/hooks/use-entity-query/use-entity-query.ts
@@ -27,7 +27,7 @@ export interface UseEntityOwnProps<TId, TItem> {
   requestType?: string;
 }
 
-export interface UseEntityQueryProps<TId, TQuery> {
+export interface UseEntityQueryProps<TId, TQuery = never> {
   id?: TId;
   query?: TQuery;
   reload?: boolean;
@@ -40,7 +40,7 @@ export interface UseEntityQueryResult<TItem> {
   error: unknown;
 }
 
-export const useEntityQuery = <TId, TItem, TQuery>(
+export const useEntityQuery = <TId, TItem, TQuery = never>(
   {
     id: entityId,
     query: entityQuery,
diff --git a/frontend/src/metabase/common/hooks/use-metric-list-query/index.ts b/frontend/src/metabase/common/hooks/use-metric-list-query/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..191fc79e4fb73949efc3d118a6f42427f3817a98
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-metric-list-query/index.ts
@@ -0,0 +1 @@
+export * from "./use-metric-list-query";
diff --git a/frontend/src/metabase/common/hooks/use-metric-list-query/use-metric-list-query.ts b/frontend/src/metabase/common/hooks/use-metric-list-query/use-metric-list-query.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13db1b8ce3a37817d231587962fe633ab56f72b1
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-metric-list-query/use-metric-list-query.ts
@@ -0,0 +1,18 @@
+import Metrics from "metabase/entities/metrics";
+import {
+  useEntityListQuery,
+  UseEntityListQueryProps,
+  UseEntityListQueryResult,
+} from "metabase/common/hooks/use-entity-list-query";
+import Metric from "metabase-lib/metadata/Metric";
+
+export const useMetricListQuery = (
+  props: UseEntityListQueryProps = {},
+): UseEntityListQueryResult<Metric> => {
+  return useEntityListQuery(props, {
+    fetchList: Metrics.actions.fetchList,
+    getList: Metrics.selectors.getList,
+    getLoading: Metrics.selectors.getLoading,
+    getError: Metrics.selectors.getError,
+  });
+};
diff --git a/frontend/src/metabase/common/hooks/use-metric-list-query/use-metric-list-query.unit.spec.tsx b/frontend/src/metabase/common/hooks/use-metric-list-query/use-metric-list-query.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e159a2d2371f6240c633c6391f86ce160f4177d6
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-metric-list-query/use-metric-list-query.unit.spec.tsx
@@ -0,0 +1,46 @@
+import React from "react";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import { createMockMetric } from "metabase-types/api/mocks";
+import { setupMetricsEndpoints } from "__support__/server-mocks";
+import {
+  renderWithProviders,
+  screen,
+  waitForElementToBeRemoved,
+} from "__support__/ui";
+import { useMetricListQuery } from "./use-metric-list-query";
+
+const TEST_METRIC = createMockMetric();
+
+const TestComponent = () => {
+  const { data = [], isLoading, error } = useMetricListQuery();
+
+  if (isLoading || error) {
+    return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
+  }
+
+  return (
+    <div>
+      {data.map(metric => (
+        <div key={metric.id}>{metric.name}</div>
+      ))}
+    </div>
+  );
+};
+
+const setup = () => {
+  setupMetricsEndpoints([TEST_METRIC]);
+  renderWithProviders(<TestComponent />);
+};
+
+describe("useMetricListQuery", () => {
+  it("should be initially loading", () => {
+    setup();
+    expect(screen.getByText("Loading...")).toBeInTheDocument();
+  });
+
+  it("should show data from the response", async () => {
+    setup();
+    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
+    expect(screen.getByText(TEST_METRIC.name)).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/common/hooks/use-metric-query/index.ts b/frontend/src/metabase/common/hooks/use-metric-query/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9c139a22d25faab57ec0fb35972414f8802c3e4a
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-metric-query/index.ts
@@ -0,0 +1 @@
+export * from "./use-metric-query";
diff --git a/frontend/src/metabase/common/hooks/use-metric-query/use-metric-query.ts b/frontend/src/metabase/common/hooks/use-metric-query/use-metric-query.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e9ebb4ba95c10ce543193ecc6b4a3e7dc35029e7
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-metric-query/use-metric-query.ts
@@ -0,0 +1,19 @@
+import Metrics from "metabase/entities/metrics";
+import {
+  useEntityQuery,
+  UseEntityQueryProps,
+  UseEntityQueryResult,
+} from "metabase/common/hooks/use-entity-query";
+import { MetricId } from "metabase-types/api";
+import Metric from "metabase-lib/metadata/Metric";
+
+export const useMetricQuery = (
+  props: UseEntityQueryProps<MetricId>,
+): UseEntityQueryResult<Metric> => {
+  return useEntityQuery(props, {
+    fetch: Metrics.actions.fetch,
+    getObject: Metrics.selectors.getObject,
+    getLoading: Metrics.selectors.getLoading,
+    getError: Metrics.selectors.getError,
+  });
+};
diff --git a/frontend/src/metabase/common/hooks/use-metric-query/use-metric-query.unit.spec.tsx b/frontend/src/metabase/common/hooks/use-metric-query/use-metric-query.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..59a08cca4ec1828d52ad3406a7318fb0706770e2
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-metric-query/use-metric-query.unit.spec.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import { createMockMetric } from "metabase-types/api/mocks";
+import { setupMetricsEndpoints } from "__support__/server-mocks";
+import {
+  renderWithProviders,
+  screen,
+  waitForElementToBeRemoved,
+} from "__support__/ui";
+import { useMetricQuery } from "./use-metric-query";
+
+const TEST_METRIC = createMockMetric();
+
+const TestComponent = () => {
+  const { data, isLoading, error } = useMetricQuery({
+    id: TEST_METRIC.id,
+  });
+
+  if (isLoading || error || !data) {
+    return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
+  }
+
+  return <div>{data.name}</div>;
+};
+
+const setup = () => {
+  setupMetricsEndpoints([TEST_METRIC]);
+  renderWithProviders(<TestComponent />);
+};
+
+describe("useMetricQuery", () => {
+  it("should be initially loading", () => {
+    setup();
+    expect(screen.getByText("Loading...")).toBeInTheDocument();
+  });
+
+  it("should show data from the response", async () => {
+    setup();
+    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
+    expect(screen.getByText(TEST_METRIC.name)).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/common/hooks/use-segment-list-query/index.ts b/frontend/src/metabase/common/hooks/use-segment-list-query/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d7d5bb5039da5a871d7fa59100614913ac7b170d
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-segment-list-query/index.ts
@@ -0,0 +1 @@
+export * from "./use-segment-list-query";
diff --git a/frontend/src/metabase/common/hooks/use-segment-list-query/use-segment-list-query.ts b/frontend/src/metabase/common/hooks/use-segment-list-query/use-segment-list-query.ts
new file mode 100644
index 0000000000000000000000000000000000000000..75cec17f77d993102167c98f58e4380607ea01c3
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-segment-list-query/use-segment-list-query.ts
@@ -0,0 +1,18 @@
+import Segments from "metabase/entities/segments";
+import {
+  useEntityListQuery,
+  UseEntityListQueryProps,
+  UseEntityListQueryResult,
+} from "metabase/common/hooks/use-entity-list-query";
+import Segment from "metabase-lib/metadata/Segment";
+
+export const useSegmentListQuery = (
+  props: UseEntityListQueryProps = {},
+): UseEntityListQueryResult<Segment> => {
+  return useEntityListQuery(props, {
+    fetchList: Segments.actions.fetchList,
+    getList: Segments.selectors.getList,
+    getLoading: Segments.selectors.getLoading,
+    getError: Segments.selectors.getError,
+  });
+};
diff --git a/frontend/src/metabase/common/hooks/use-segment-list-query/use-segment-list-query.unit.spec.tsx b/frontend/src/metabase/common/hooks/use-segment-list-query/use-segment-list-query.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..239b8d992707d68747acf2ec2f24b8b23e18014f
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-segment-list-query/use-segment-list-query.unit.spec.tsx
@@ -0,0 +1,46 @@
+import React from "react";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import { createMockSegment } from "metabase-types/api/mocks";
+import { setupSegmentsEndpoints } from "__support__/server-mocks";
+import {
+  renderWithProviders,
+  screen,
+  waitForElementToBeRemoved,
+} from "__support__/ui";
+import { useSegmentListQuery } from "./use-segment-list-query";
+
+const TEST_SEGMENT = createMockSegment();
+
+const TestComponent = () => {
+  const { data = [], isLoading, error } = useSegmentListQuery();
+
+  if (isLoading || error) {
+    return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
+  }
+
+  return (
+    <div>
+      {data.map(segment => (
+        <div key={segment.id}>{segment.name}</div>
+      ))}
+    </div>
+  );
+};
+
+const setup = () => {
+  setupSegmentsEndpoints([TEST_SEGMENT]);
+  renderWithProviders(<TestComponent />);
+};
+
+describe("useSegmentListQuery", () => {
+  it("should be initially loading", () => {
+    setup();
+    expect(screen.getByText("Loading...")).toBeInTheDocument();
+  });
+
+  it("should show data from the response", async () => {
+    setup();
+    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
+    expect(screen.getByText(TEST_SEGMENT.name)).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/common/hooks/use-segment-query/index.ts b/frontend/src/metabase/common/hooks/use-segment-query/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7617b6e8c81088f1a70dc98c45ef7fa6e9e05e9d
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-segment-query/index.ts
@@ -0,0 +1 @@
+export * from "./use-segment-query";
diff --git a/frontend/src/metabase/common/hooks/use-segment-query/use-segment-query.ts b/frontend/src/metabase/common/hooks/use-segment-query/use-segment-query.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a7d4b3e3339199b8c72c29367cdcaa60f4ee2a86
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-segment-query/use-segment-query.ts
@@ -0,0 +1,19 @@
+import Segments from "metabase/entities/segments";
+import {
+  useEntityQuery,
+  UseEntityQueryProps,
+  UseEntityQueryResult,
+} from "metabase/common/hooks/use-entity-query";
+import { SegmentId } from "metabase-types/api";
+import Segment from "metabase-lib/metadata/Segment";
+
+export const useSegmentQuery = (
+  props: UseEntityQueryProps<SegmentId>,
+): UseEntityQueryResult<Segment> => {
+  return useEntityQuery(props, {
+    fetch: Segments.actions.fetch,
+    getObject: Segments.selectors.getObject,
+    getLoading: Segments.selectors.getLoading,
+    getError: Segments.selectors.getError,
+  });
+};
diff --git a/frontend/src/metabase/common/hooks/use-segment-query/use-segment-query.unit.spec.tsx b/frontend/src/metabase/common/hooks/use-segment-query/use-segment-query.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..01190b36d53e89abad8ad10eac04e31b6a54b2e0
--- /dev/null
+++ b/frontend/src/metabase/common/hooks/use-segment-query/use-segment-query.unit.spec.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
+import { createMockSegment } from "metabase-types/api/mocks";
+import { setupSegmentsEndpoints } from "__support__/server-mocks";
+import {
+  renderWithProviders,
+  screen,
+  waitForElementToBeRemoved,
+} from "__support__/ui";
+import { useSegmentQuery } from "./use-segment-query";
+
+const TEST_SEGMENT = createMockSegment();
+
+const TestComponent = () => {
+  const { data, isLoading, error } = useSegmentQuery({
+    id: TEST_SEGMENT.id,
+  });
+
+  if (isLoading || error || !data) {
+    return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
+  }
+
+  return <div>{data.name}</div>;
+};
+
+const setup = () => {
+  setupSegmentsEndpoints([TEST_SEGMENT]);
+  renderWithProviders(<TestComponent />);
+};
+
+describe("useSegmentQuery", () => {
+  it("should be initially loading", () => {
+    setup();
+    expect(screen.getByText("Loading...")).toBeInTheDocument();
+  });
+
+  it("should show data from the response", async () => {
+    setup();
+    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
+    expect(screen.getByText(TEST_SEGMENT.name)).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/query_builder/components/dataref/SchemaPane.tsx b/frontend/src/metabase/query_builder/components/dataref/SchemaPane.tsx
index 6ea9533ca0287136fb88abc4a645c57cd50d47ca..802869b1415b8b04968d07d09e14ad1799a5f7f8 100644
--- a/frontend/src/metabase/query_builder/components/dataref/SchemaPane.tsx
+++ b/frontend/src/metabase/query_builder/components/dataref/SchemaPane.tsx
@@ -30,8 +30,8 @@ const SchemaPane = ({
   schema,
 }: SchemaPaneProps) => {
   const tables = useMemo(
-    () => schema.tables.sort((a, b) => a.name.localeCompare(b.name)),
-    [schema.tables],
+    () => schema.getTables().sort((a, b) => a.name.localeCompare(b.name)),
+    [schema],
   );
   return (
     <SidebarContent
diff --git a/frontend/test/__support__/server-mocks/database.ts b/frontend/test/__support__/server-mocks/database.ts
index 6f3439320f35add5b006cf48ff319fc959e1bc41..169dde73e56d25421a6102bacfb8690dffac6083 100644
--- a/frontend/test/__support__/server-mocks/database.ts
+++ b/frontend/test/__support__/server-mocks/database.ts
@@ -45,9 +45,11 @@ export const setupSchemaEndpoints = (db: Database) => {
 };
 
 export function setupDatabaseIdFieldsEndpoints({ id, tables = [] }: Database) {
-  const fields = tables
-    .flatMap(table => table.fields ?? [])
-    .filter(field => isTypeFK(field.semantic_type));
+  const fields = tables.flatMap(table =>
+    (table.fields ?? [])
+      .filter(field => isTypeFK(field.semantic_type))
+      .map(field => ({ ...field, table })),
+  );
 
   fetchMock.get(`path:/api/database/${id}/idfields`, fields);
 }
diff --git a/frontend/test/__support__/server-mocks/index.ts b/frontend/test/__support__/server-mocks/index.ts
index 051d40a738ee28614c72ca24b412892a1249a7a6..79c0fdc2ae95841ead71fc1c05cbadef0bfce96c 100644
--- a/frontend/test/__support__/server-mocks/index.ts
+++ b/frontend/test/__support__/server-mocks/index.ts
@@ -9,7 +9,10 @@ export * from "./dashboard";
 export * from "./database";
 export * from "./dataset";
 export * from "./field";
+export * from "./metabot";
+export * from "./metric";
 export * from "./search";
+export * from "./segment";
 export * from "./session";
 export * from "./settings";
 export * from "./setup";
diff --git a/frontend/test/__support__/server-mocks/metric.ts b/frontend/test/__support__/server-mocks/metric.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9c6ed2422b4ea97831a2183fdf4f83e631669228
--- /dev/null
+++ b/frontend/test/__support__/server-mocks/metric.ts
@@ -0,0 +1,11 @@
+import fetchMock from "fetch-mock";
+import { Metric } from "metabase-types/api";
+
+export function setupMetricEndpoint(metric: Metric) {
+  fetchMock.get(`path:/api/metric/${metric.id}`, metric);
+}
+
+export function setupMetricsEndpoints(metrics: Metric[]) {
+  fetchMock.get(`path:/api/metric`, metrics);
+  metrics.forEach(metric => setupMetricEndpoint(metric));
+}
diff --git a/frontend/test/__support__/server-mocks/segment.ts b/frontend/test/__support__/server-mocks/segment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e47264844aef545fb684ef3044593fb68cbd0a1
--- /dev/null
+++ b/frontend/test/__support__/server-mocks/segment.ts
@@ -0,0 +1,11 @@
+import fetchMock from "fetch-mock";
+import { Segment } from "metabase-types/api";
+
+export function setupSegmentEndpoint(segment: Segment) {
+  fetchMock.get(`path:/api/segment/${segment.id}`, segment);
+}
+
+export function setupSegmentsEndpoints(segments: Segment[]) {
+  fetchMock.get(`path:/api/segment`, segments);
+  segments.forEach(segment => setupSegmentEndpoint(segment));
+}