diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn
index 13946c9b0856d623359009330fc9bc5dfc51d141..d501137b5709bf5c0bf70884741712e8545d8583 100644
--- a/.clj-kondo/config.edn
+++ b/.clj-kondo/config.edn
@@ -361,6 +361,9 @@
     metabase.query-processor.timezone                               qp.timezone
     metabase.query-processor.util                                   qp.util
     metabase.related                                                related
+    metabase.search.config                                          search.config
+    metabase.search.filter                                          search.filter
+    metabase.search.util                                            search.util
     metabase.search.scoring                                         scoring
     metabase.server.middleware.auth                                 mw.auth
     metabase.server.middleware.browser-cookie                       mw.browser-cookie
diff --git a/e2e/test/scenarios/models/model-indexes.cy.spec.js b/e2e/test/scenarios/models/model-indexes.cy.spec.js
index 73584aeb287bfdd0e782bc2815da4f7ff05657cc..c377ba0140b3667e511fcaae3e24d50656997f3d 100644
--- a/e2e/test/scenarios/models/model-indexes.cy.spec.js
+++ b/e2e/test/scenarios/models/model-indexes.cy.spec.js
@@ -126,7 +126,9 @@ describe("scenarios > model indexes", () => {
       .findByPlaceholderText("Search…")
       .type("marble shoes");
 
-    cy.findByTestId("search-results-list").findByText(/didn't find anything/i);
+    cy.wait("@searchQuery");
+
+    cy.findByTestId("search-results-empty-state").should("be.visible");
   });
 
   it("should be able to search model index values and visit detail records", () => {
diff --git a/e2e/test/scenarios/models/models.cy.spec.js b/e2e/test/scenarios/models/models.cy.spec.js
index 8b11971c8825a36af635c3e83bb79c5ef83c5e84..6e7070764b8d3a9a2fe9fc897a041fcafd6b11ea 100644
--- a/e2e/test/scenarios/models/models.cy.spec.js
+++ b/e2e/test/scenarios/models/models.cy.spec.js
@@ -610,9 +610,38 @@ function testDataPickerSearch({
   cy.findByPlaceholderText(inputPlaceholderText).type(query);
   cy.wait("@search");
 
-  cy.findAllByText(/Model in/i).should(models ? "exist" : "not.exist");
-  cy.findAllByText(/Saved question in/i).should(cards ? "exist" : "not.exist");
-  cy.findAllByText(/Table in/i).should(tables ? "exist" : "not.exist");
+  const searchResultItems = cy.findAllByTestId("search-result-item");
+
+  searchResultItems.then($results => {
+    const modelTypes = {};
+
+    for (const htmlElement of $results.toArray()) {
+      const type = htmlElement.getAttribute("data-model-type");
+      if (type in modelTypes) {
+        modelTypes[type] += 1;
+      } else {
+        modelTypes[type] = 1;
+      }
+    }
+
+    if (models) {
+      expect(modelTypes["dataset"]).to.be.greaterThan(0);
+    } else {
+      expect(Object.keys(modelTypes)).not.to.include("dataset");
+    }
+
+    if (cards) {
+      expect(modelTypes["card"]).to.be.greaterThan(0);
+    } else {
+      expect(Object.keys(modelTypes)).not.to.include("card");
+    }
+
+    if (tables) {
+      expect(modelTypes["table"]).to.be.greaterThan(0);
+    } else {
+      expect(Object.keys(modelTypes)).not.to.include("table");
+    }
+  });
 
   cy.icon("close").click();
 }
diff --git a/e2e/test/scenarios/onboarding/search/recently-viewed.cy.spec.js b/e2e/test/scenarios/onboarding/search/recently-viewed.cy.spec.js
index 4627d94b30b3acdeb028343a2df802d62e61fc28..4e22e2deec6837f7912e509eaf1f9abbc7e15f3d 100644
--- a/e2e/test/scenarios/onboarding/search/recently-viewed.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/search/recently-viewed.cy.spec.js
@@ -7,15 +7,11 @@ import {
   setTokenFeatures,
 } from "e2e/support/helpers";
 
-import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
-import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import {
   ORDERS_QUESTION_ID,
   ORDERS_DASHBOARD_ID,
 } from "e2e/support/cypress_sample_instance_data";
 
-const { PEOPLE_ID } = SAMPLE_DATABASE;
-
 describe("search > recently viewed", () => {
   beforeEach(() => {
     restore();
@@ -44,24 +40,9 @@ describe("search > recently viewed", () => {
   });
 
   it("shows list of recently viewed items", () => {
-    assertRecentlyViewedItem(
-      0,
-      "Orders in a dashboard",
-      "Dashboard",
-      `/dashboard/${ORDERS_DASHBOARD_ID}-orders-in-a-dashboard`,
-    );
-    assertRecentlyViewedItem(
-      1,
-      "Orders",
-      "Question",
-      `/question/${ORDERS_QUESTION_ID}-orders`,
-    );
-    assertRecentlyViewedItem(
-      2,
-      "People",
-      "Table",
-      `/question#?db=${SAMPLE_DB_ID}&table=${PEOPLE_ID}`,
-    );
+    assertRecentlyViewedItem(0, "Orders in a dashboard", "Dashboard");
+    assertRecentlyViewedItem(1, "Orders", "Question");
+    assertRecentlyViewedItem(2, "People", "Table");
   });
 
   it("allows to select an item from keyboard", () => {
@@ -95,25 +76,15 @@ describeEE("search > recently viewed > enterprise features", () => {
   it("should show verified badge in the 'Recently viewed' list (metabase#18021)", () => {
     cy.findByPlaceholderText("Search…").click();
 
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("Recently viewed")
-      .parent()
-      .within(() => {
-        cy.findByText("Orders").closest("a").find(".Icon-verified");
-      });
+    cy.findByTestId("recently-viewed-item").within(() => {
+      cy.icon("verified_filled").should("be.visible");
+    });
   });
 });
 
-const assertRecentlyViewedItem = (index, title, type, link) => {
-  cy.findAllByTestId("recently-viewed-item")
-    .eq(index)
-    .parent()
-    .should("have.attr", "href", link);
-
+const assertRecentlyViewedItem = (index, title, type) => {
   cy.findAllByTestId("recently-viewed-item-title")
     .eq(index)
     .should("have.text", title);
-  cy.findAllByTestId("recently-viewed-item-type")
-    .eq(index)
-    .should("have.text", type);
+  cy.findAllByTestId("result-link-wrapper").eq(index).should("have.text", type);
 };
diff --git a/e2e/test/scenarios/onboarding/search/search.cy.spec.js b/e2e/test/scenarios/onboarding/search/search.cy.spec.js
index 2df30ce7d25a29d5d091c99dc0514a9628c6f70a..aeb10033b41e7639468d6a2ed13e6528a39b7321 100644
--- a/e2e/test/scenarios/onboarding/search/search.cy.spec.js
+++ b/e2e/test/scenarios/onboarding/search/search.cy.spec.js
@@ -1,82 +1,130 @@
 import {
   createAction,
+  describeEE,
   describeWithSnowplow,
   enableTracking,
   expectGoodSnowplowEvents,
   expectNoBadSnowplowEvents,
+  modal,
   popover,
   resetSnowplow,
   restore,
   setActionsEnabledForDB,
+  setTokenFeatures,
+  summarize,
 } from "e2e/support/helpers";
-import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data";
+import {
+  ADMIN_USER_ID,
+  NORMAL_USER_ID,
+  ORDERS_COUNT_QUESTION_ID,
+  ORDERS_QUESTION_ID,
+} from "e2e/support/cypress_sample_instance_data";
 import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
 
 const typeFilters = [
   {
     label: "Question",
-    filterName: "card",
-    resultInfoText: "Saved question in",
+    type: "card",
   },
   {
     label: "Dashboard",
-    filterName: "dashboard",
-    resultInfoText: "Dashboard in",
+    type: "dashboard",
   },
   {
     label: "Collection",
-    filterName: "collection",
-    resultInfoText: "Collection",
+    type: "collection",
   },
   {
     label: "Table",
-    filterName: "table",
-    resultInfoText: "Table in",
+    type: "table",
   },
   {
     label: "Database",
-    filterName: "database",
-    resultInfoText: "Database",
+    type: "database",
   },
   {
     label: "Model",
-    filterName: "dataset",
-    resultInfoText: "Model in",
+    type: "dataset",
   },
   {
     label: "Action",
-    filterName: "action",
+    type: "action",
   },
 ];
 
 const { ORDERS_ID } = SAMPLE_DATABASE;
 
+const NORMAL_USER_TEST_QUESTION = {
+  name: `Robert's Super Duper Reviews`,
+  query: { "source-table": ORDERS_ID, limit: 1 },
+  collection_id: null,
+};
+
+const ADMIN_TEST_QUESTION = {
+  name: `Admin Super Duper Reviews`,
+  query: { "source-table": ORDERS_ID, limit: 1 },
+  collection_id: null,
+};
+
+// Using these names in the `last_edited_by` section to reduce confusion
+const LAST_EDITED_BY_ADMIN_QUESTION = NORMAL_USER_TEST_QUESTION;
+const LAST_EDITED_BY_NORMAL_USER_QUESTION = ADMIN_TEST_QUESTION;
+
+const REVIEWS_TABLE_NAME = "Reviews";
+
+const TEST_NATIVE_QUESTION_NAME = "GithubUptimeisMagnificentlyHigh";
+
+const TEST_CREATED_AT_FILTERS = [
+  ["Today", "thisday"],
+  ["Yesterday", "past1days"],
+  ["Previous Week", "past1weeks"],
+  ["Previous 7 Days", "past7days"],
+  ["Previous 30 Days", "past30days"],
+  ["Previous Month", "past1months"],
+  ["Previous 3 Months", "past3months"],
+  ["Previous 12 Months", "past12months"],
+];
+
 describe("scenarios > search", () => {
   beforeEach(() => {
     restore();
     cy.intercept("GET", "/api/search?q=*").as("search");
+    cy.signInAsAdmin();
   });
 
   describe("universal search", () => {
     it("should work for admin (metabase#20018)", () => {
-      cy.signInAsAdmin();
-
       cy.visit("/");
       getSearchBar().as("searchBox").type("product").blur();
 
-      cy.findByTestId("search-results-list").within(() => {
-        getProductsSearchResults();
+      cy.wait("@search");
+
+      expectSearchResultContent({
+        expectedSearchResults: [
+          {
+            name: "Products",
+            description:
+              "Includes a catalog of all the products ever sold by the famed Sample Company.",
+            collection: "Sample Database",
+          },
+        ],
+        strict: false,
       });
 
       cy.get("@searchBox").type("{enter}");
       cy.wait("@search");
 
-      cy.findAllByTestId("search-result-item")
-        .first()
-        .within(() => {
-          getProductsSearchResults();
-        });
+      expectSearchResultContent({
+        expectedSearchResults: [
+          {
+            name: "Products",
+            description:
+              "Includes a catalog of all the products ever sold by the famed Sample Company.",
+          },
+        ],
+        strict: false,
+      });
     });
 
     it("should work for user with permissions (metabase#12332)", () => {
@@ -122,41 +170,41 @@ describe("scenarios > search", () => {
       cy.get("@search.all").should("have.length", 1);
     });
   });
+  describe("accessing full page search with `Enter`", () => {
+    it("should not render full page search if user has not entered a text query", () => {
+      cy.intercept("GET", "/api/activity/recent_views").as("getRecentViews");
 
-  describe("applying search filters", () => {
-    beforeEach(() => {
-      cy.signInAsAdmin();
-
-      setActionsEnabledForDB(SAMPLE_DB_ID);
-
-      cy.createQuestion({
-        name: "Orders Model",
-        query: { "source-table": ORDERS_ID },
-        dataset: true,
-      }).then(({ body: { id } }) => {
-        createAction({
-          name: "Update orders quantity",
-          description: "Set orders quantity to the same value",
-          type: "query",
-          model_id: id,
-          database_id: SAMPLE_DB_ID,
-          dataset_query: {
-            database: SAMPLE_DB_ID,
-            native: {
-              query: "UPDATE orders SET quantity = quantity",
-            },
-            type: "native",
-          },
-          parameters: [],
-          visualization_settings: {
-            type: "button",
-          },
-        });
+      cy.visit("/");
+
+      getSearchBar().click().type("{enter}");
+
+      cy.wait("@getRecentViews");
+
+      cy.findByTestId("search-results-floating-container").within(() => {
+        cy.findByText("Recently viewed").should("exist");
       });
+      cy.location("pathname").should("eq", "/");
     });
 
-    describe("hydrating search query from URL", () => {
-      it("should hydrate search with search text", () => {
+    it("should render full page search when search text is present and user clicks 'Enter'", () => {
+      cy.visit("/");
+
+      getSearchBar().click().type("orders{enter}");
+      cy.wait("@search");
+
+      cy.findByTestId("search-app").within(() => {
+        cy.findByText('Results for "orders"').should("exist");
+      });
+
+      cy.location().should(loc => {
+        expect(loc.pathname).to.eq("/search");
+        expect(loc.search).to.eq("?q=orders");
+      });
+    });
+  });
+  describe("applying search filters", () => {
+    describe("no filters", () => {
+      it("hydrating search from URL", () => {
         cy.visit("/search?q=orders");
         cy.wait("@search");
 
@@ -165,110 +213,864 @@ describe("scenarios > search", () => {
           cy.findByText('Results for "orders"').should("exist");
         });
       });
+    });
 
-      it("should hydrate search with search text and filter", () => {
-        const { filterName, resultInfoText } = typeFilters[0];
-        cy.visit(`/search?q=orders&type=${filterName}`);
-        cy.wait("@search");
+    describe("type filter", () => {
+      beforeEach(() => {
+        setActionsEnabledForDB(SAMPLE_DB_ID);
 
-        getSearchBar().should("have.value", "orders");
+        cy.createQuestion({
+          name: "Orders Model",
+          query: { "source-table": ORDERS_ID },
+          dataset: true,
+        }).then(({ body: { id } }) => {
+          createAction({
+            name: "Update orders quantity",
+            description: "Set orders quantity to the same value",
+            type: "query",
+            model_id: id,
+            database_id: SAMPLE_DB_ID,
+            dataset_query: {
+              database: SAMPLE_DB_ID,
+              native: {
+                query: "UPDATE orders SET quantity = quantity",
+              },
+              type: "native",
+            },
+            parameters: [],
+            visualization_settings: {
+              type: "button",
+            },
+          });
+        });
+      });
 
-        cy.findByTestId("search-app").within(() => {
-          cy.findByText('Results for "orders"').should("exist");
+      typeFilters.forEach(({ label, type }) => {
+        it(`should hydrate search with search text and ${label} filter`, () => {
+          cy.visit(`/search?q=e&type=${type}`);
+          cy.wait("@search");
+
+          getSearchBar().should("have.value", "e");
+
+          cy.findByTestId("search-app").within(() => {
+            cy.findByText('Results for "e"').should("exist");
+          });
+
+          const regex = new RegExp(`${type}$`);
+          cy.findAllByTestId("search-result-item").each(result => {
+            cy.wrap(result)
+              .should("have.attr", "aria-label")
+              .and("match", regex);
+          });
+
+          cy.findByTestId("type-search-filter").within(() => {
+            cy.findByText(label).should("exist");
+            cy.findByLabelText("close icon").should("exist");
+          });
         });
 
-        cy.findAllByTestId("result-link-info-text").each(result => {
-          cy.wrap(result).should("contain.text", resultInfoText);
+        it(`should filter results by ${label}`, () => {
+          cy.visit("/");
+
+          getSearchBar().clear().type("e{enter}");
+          cy.wait("@search");
+
+          cy.findByTestId("type-search-filter").click();
+          popover().within(() => {
+            cy.findByText(label).click();
+            cy.findByText("Apply").click();
+          });
+
+          const regex = new RegExp(`${type}$`);
+          cy.findAllByTestId("search-result-item").each(result => {
+            cy.wrap(result)
+              .should("have.attr", "aria-label")
+              .and("match", regex);
+          });
         });
+      });
+
+      it("should remove type filter when `X` is clicked on search filter", () => {
+        const { label, type } = typeFilters[0];
+        cy.visit(`/search?q=e&type=${type}`);
+        cy.wait("@search");
 
         cy.findByTestId("type-search-filter").within(() => {
-          cy.findByText("Question").should("exist");
+          cy.findByText(label).should("exist");
+          cy.findByLabelText("close icon").click();
+          cy.findByText(label).should("not.exist");
+          cy.findByText("Content type").should("exist");
+        });
+
+        cy.url().should("not.contain", "type");
+
+        cy.findAllByTestId("search-result-item").then($results => {
+          const uniqueResults = new Set(
+            $results.toArray().map(el => {
+              const label = el.getAttribute("aria-label");
+              return label.split(" ").slice(-1)[0];
+            }),
+          );
+          expect(uniqueResults.size).to.be.greaterThan(1);
+        });
+      });
+    });
+
+    describe("created_by filter", () => {
+      beforeEach(() => {
+        restore();
+        // create a question from a normal and admin user, then we can query the question
+        // created by that user as an admin
+        cy.signInAsNormalUser();
+        cy.createQuestion(NORMAL_USER_TEST_QUESTION);
+        cy.signOut();
+
+        cy.signInAsAdmin();
+        cy.createQuestion(ADMIN_TEST_QUESTION);
+      });
+
+      it("should hydrate created_by filter", () => {
+        cy.visit(
+          `/search?created_by=${ADMIN_USER_ID}&created_by=${NORMAL_USER_ID}&q=reviews`,
+        );
+
+        cy.wait("@search");
+
+        cy.findByTestId("created_by-search-filter").within(() => {
+          cy.findByText("2 users selected").should("exist");
           cy.findByLabelText("close icon").should("exist");
         });
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+            {
+              name: ADMIN_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+      });
+
+      it("should filter results by one user", () => {
+        cy.visit("/");
+
+        getSearchBar().clear();
+        getSearchBar().type("reviews{enter}");
+        cy.wait("@search");
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            NORMAL_USER_TEST_QUESTION.name,
+            ADMIN_TEST_QUESTION.name,
+            REVIEWS_TABLE_NAME,
+          ],
+        });
+
+        cy.findByTestId("created_by-search-filter").click();
+
+        popover().within(() => {
+          cy.findByText("Robert Tableton").click();
+          cy.findByText("Apply").click();
+        });
+        cy.url().should("contain", "created_by");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+          ],
+        });
+      });
+      it("should filter results by more than one user", () => {
+        cy.visit("/");
+
+        getSearchBar().clear().type("reviews{enter}");
+        cy.wait("@search");
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            NORMAL_USER_TEST_QUESTION.name,
+            ADMIN_TEST_QUESTION.name,
+            REVIEWS_TABLE_NAME,
+          ],
+        });
+
+        cy.findByTestId("created_by-search-filter").click();
+
+        popover().within(() => {
+          cy.findByText("Robert Tableton").click();
+          cy.findByText("Bobby Tables").click();
+          cy.findByText("Apply").click();
+        });
+        cy.url().should("contain", "created_by");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+            {
+              name: ADMIN_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+      });
+
+      it("should be able to remove a user from the `created_by` filter", () => {
+        cy.visit(
+          `/search?q=reviews&created_by=${NORMAL_USER_ID}&created_by=${ADMIN_USER_ID}`,
+        );
+
+        cy.wait("@search");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+            {
+              name: ADMIN_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+
+        cy.findByTestId("created_by-search-filter").click();
+        popover().within(() => {
+          // remove Robert Tableton from the created_by filter
+          cy.findByTestId("search-user-select-box")
+            .findByText("Robert Tableton")
+            .click();
+          cy.findByText("Apply").click();
+        });
+
+        expectSearchResultItemNameContent({
+          itemNames: [ADMIN_TEST_QUESTION.name],
+        });
+      });
+
+      it("should remove created_by filter when `X` is clicked on filter", () => {
+        cy.visit(`/search?q=reviews&created_by=${NORMAL_USER_ID}`);
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              timestamp: "Created a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+          ],
+        });
+
+        cy.findByTestId("created_by-search-filter").within(() => {
+          cy.findByText("Robert Tableton").should("exist");
+          cy.findByLabelText("close icon").click();
+        });
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            NORMAL_USER_TEST_QUESTION.name,
+            ADMIN_TEST_QUESTION.name,
+            REVIEWS_TABLE_NAME,
+          ],
+        });
       });
     });
 
-    describe("accessing full page search with `Enter`", () => {
-      it("should not render full page search if user has not entered a text query ", () => {
-        cy.intercept("GET", "/api/activity/recent_views").as("getRecentViews");
+    describe("last_edited_by filter", () => {
+      beforeEach(() => {
+        cy.signInAsAdmin();
+        // We'll create a question as a normal user, then edit it as an admin user
+        cy.createQuestion(LAST_EDITED_BY_NORMAL_USER_QUESTION).then(
+          ({ body: { id: questionId } }) => {
+            cy.signOut();
+            cy.signInAsNormalUser();
+            cy.visit(`/question/${questionId}`);
+            summarize();
+            cy.findByTestId("sidebar-right").findByText("Done").click();
+            cy.findByTestId("qb-header-action-panel")
+              .findByText("Save")
+              .click();
+            modal().findByText("Save").click();
+          },
+        );
+
+        // We'll create a question as an admin user, then edit it as a normal user
+        cy.createQuestion(LAST_EDITED_BY_ADMIN_QUESTION).then(
+          ({ body: { id: questionId } }) => {
+            cy.signInAsAdmin();
+            cy.visit(`/question/${questionId}`);
+            summarize();
+            cy.findByTestId("sidebar-right").findByText("Done").click();
+            cy.findByTestId("qb-header-action-panel")
+              .findByText("Save")
+              .click();
+            modal().findByText("Save").click();
+          },
+        );
+      });
+
+      it("should hydrate last_edited_by filter", () => {
+        cy.intercept("GET", "/api/user").as("getUsers");
+
+        cy.visit(`/search?q=reviews&last_edited_by=${NORMAL_USER_ID}`);
 
+        cy.wait("@search");
+
+        cy.findByTestId("last_edited_by-search-filter").within(() => {
+          cy.findByText("Robert Tableton").should("exist");
+          cy.findByLabelText("close icon").should("exist");
+        });
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+          ],
+        });
+      });
+
+      it("should filter last_edited results by one user", () => {
         cy.visit("/");
 
-        getSearchBar().click().type("{enter}");
+        getSearchBar().clear().type("reviews{enter}");
+        cy.wait("@search");
 
-        cy.wait("@getRecentViews");
+        cy.findByTestId("last_edited_by-search-filter").click();
 
-        cy.findByTestId("search-results-floating-container").within(() => {
-          cy.findByText("Recently viewed").should("exist");
+        expectSearchResultItemNameContent({
+          itemNames: [
+            LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+            LAST_EDITED_BY_ADMIN_QUESTION.name,
+            REVIEWS_TABLE_NAME,
+          ],
+        });
+
+        popover().within(() => {
+          cy.findByText("Robert Tableton").click();
+          cy.findByText("Apply").click();
+        });
+        cy.url().should("contain", "last_edited_by");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+          ],
         });
-        cy.location("pathname").should("eq", "/");
       });
 
-      it("should render full page search when search text is present and user clicks 'Enter'", () => {
+      it("should filter last_edited results by more than user", () => {
         cy.visit("/");
 
-        getSearchBar().click().type("orders{enter}");
+        getSearchBar().clear().type("reviews{enter}");
         cy.wait("@search");
 
-        cy.findByTestId("search-app").within(() => {
-          cy.findByText('Results for "orders"').should("exist");
+        cy.findByTestId("last_edited_by-search-filter").click();
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+            LAST_EDITED_BY_ADMIN_QUESTION.name,
+            REVIEWS_TABLE_NAME,
+          ],
+        });
+
+        popover().within(() => {
+          cy.findByText("Robert Tableton").click();
+          cy.findByText("Bobby Tables").click();
+          cy.findByText("Apply").click();
         });
+        cy.url().should("contain", "last_edited_by");
 
-        cy.location().should(loc => {
-          expect(loc.pathname).to.eq("/search");
-          expect(loc.search).to.eq("?q=orders");
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+            {
+              name: LAST_EDITED_BY_ADMIN_QUESTION.name,
+              timestamp: "Updated a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+      });
+
+      it("should allow to remove a user from the `last_edited_by` filter", () => {
+        cy.visit(
+          `/search?q=reviews&last_edited_by=${NORMAL_USER_ID}&last_edited_by=${ADMIN_USER_ID}`,
+        );
+
+        cy.wait("@search");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+            {
+              name: LAST_EDITED_BY_ADMIN_QUESTION.name,
+              timestamp: "Updated a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+
+        cy.findByTestId("last_edited_by-search-filter").click();
+        popover().within(() => {
+          // remove Robert Tableton from the last_edited_by filter
+          cy.findByTestId("search-user-select-box")
+            .findByText("Robert Tableton")
+            .click();
+          cy.findByText("Apply").click();
+        });
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_ADMIN_QUESTION.name,
+              timestamp: "Updated a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+      });
+
+      it("should remove last_edited_by filter when `X` is clicked on filter", () => {
+        cy.visit(
+          `/search?q=reviews&last_edited_by=${NORMAL_USER_ID}&last_edited_by=${ADMIN_USER_ID}`,
+        );
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+              collection: "Our analytics",
+            },
+            {
+              name: LAST_EDITED_BY_ADMIN_QUESTION.name,
+              timestamp: "Updated a few seconds ago by you",
+              collection: "Our analytics",
+            },
+          ],
+        });
+
+        cy.findByTestId("last_edited_by-search-filter").within(() => {
+          cy.findByText("2 users selected").should("exist");
+          cy.findByLabelText("close icon").click();
+        });
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+            LAST_EDITED_BY_ADMIN_QUESTION.name,
+            REVIEWS_TABLE_NAME,
+          ],
         });
       });
     });
 
-    describe("search filters", () => {
-      describe("type filters", () => {
-        typeFilters.forEach(({ label, resultInfoText }) => {
-          it(`should filter results by ${label}`, () => {
-            cy.visit("/");
-
-            getSearchBar().clear().type("e{enter}");
-            cy.wait("@search");
-
-            cy.findByTestId("type-search-filter").click();
-            popover().within(() => {
-              cy.findByText(label).click();
-              cy.findByText("Apply filters").click();
-            });
-
-            cy.findAllByTestId("result-link-info-text").each(result => {
-              if (resultInfoText) {
-                cy.wrap(result).should("contain.text", resultInfoText);
-              }
-            });
+    describe("created_at filter", () => {
+      beforeEach(() => {
+        cy.signInAsNormalUser();
+        cy.createQuestion(NORMAL_USER_TEST_QUESTION);
+        cy.signOut();
+        cy.signInAsAdmin();
+      });
+
+      TEST_CREATED_AT_FILTERS.forEach(([label, filter]) => {
+        it(`should hydrate created_at=${filter}`, () => {
+          cy.visit(`/search?q=orders&created_at=${filter}`);
+
+          cy.wait("@search");
+
+          cy.findByTestId("created_at-search-filter").within(() => {
+            cy.findByText(label).should("exist");
+            cy.findByLabelText("close icon").should("exist");
           });
         });
+      });
+
+      // we can only test the 'today' filter since we currently
+      // can't edit the created_at column of a question in our database
+      it(`should filter results by Today (created_at=thisday)`, () => {
+        cy.visit(`/search?q=Reviews`);
+
+        expectSearchResultItemNameContent(
+          {
+            itemNames: [REVIEWS_TABLE_NAME, NORMAL_USER_TEST_QUESTION.name],
+          },
+          { strict: false },
+        );
+
+        cy.findByTestId("created_at-search-filter").click();
+        popover().within(() => {
+          cy.findByText("Today").click();
+        });
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              collection: "Our analytics",
+              timestamp: "Created a few seconds ago by Robert Tableton",
+            },
+          ],
+          strict: false,
+        });
+      });
+
+      it("should remove created_at filter when `X` is clicked on search filter", () => {
+        cy.visit(`/search?q=Reviews&created_at=thisday`);
+        cy.wait("@search");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: NORMAL_USER_TEST_QUESTION.name,
+              collection: "Our analytics",
+              timestamp: "Created a few seconds ago by Robert Tableton",
+            },
+          ],
+          strict: false,
+        });
+
+        cy.findByTestId("created_at-search-filter").within(() => {
+          cy.findByText("Today").should("exist");
+
+          cy.findByLabelText("close icon").click();
+
+          cy.findByText("Today").should("not.exist");
+          cy.findByText("Creation date").should("exist");
+        });
+
+        cy.url().should("not.contain", "created_at");
+
+        expectSearchResultItemNameContent(
+          {
+            itemNames: [REVIEWS_TABLE_NAME, NORMAL_USER_TEST_QUESTION.name],
+          },
+          { strict: false },
+        );
+      });
+    });
+
+    describe("last_edited_at filter", () => {
+      beforeEach(() => {
+        cy.signInAsAdmin();
+        // We'll create a question as a normal user, then edit it as an admin user
+        cy.createQuestion(LAST_EDITED_BY_NORMAL_USER_QUESTION).then(
+          ({ body: { id: questionId } }) => {
+            cy.signOut();
+            cy.signInAsNormalUser();
+            cy.visit(`/question/${questionId}`);
+            summarize();
+            cy.findByTestId("sidebar-right").findByText("Done").click();
+            cy.findByTestId("qb-header-action-panel")
+              .findByText("Save")
+              .click();
+            modal().findByText("Save").click();
+            cy.signOut();
+            cy.signInAsAdmin();
+          },
+        );
+      });
+
+      TEST_CREATED_AT_FILTERS.forEach(([label, filter]) => {
+        it(`should hydrate last_edited_at=${filter}`, () => {
+          cy.visit(`/search?q=reviews&last_edited_at=${filter}`);
 
-        it("should remove type filter when `X` is clicked on search filter", () => {
-          const { filterName } = typeFilters[0];
-          cy.visit(`/search?q=orders&type=${filterName}`);
           cy.wait("@search");
 
-          cy.findByTestId("type-search-filter").within(() => {
-            cy.findByText("Question").should("exist");
-            cy.findByLabelText("close icon").click();
-            cy.findByText("Question").should("not.exist");
-            cy.findByText("Content type").should("exist");
+          cy.findByTestId("last_edited_at-search-filter").within(() => {
+            cy.findByText(label).should("exist");
+            cy.findByLabelText("close icon").should("exist");
+          });
+        });
+      });
+
+      // we can only test the 'today' filter since we currently
+      // can't edit the last_edited_at column of a question in our database
+      it(`should filter results by Today (last_edited_at=thisday)`, () => {
+        cy.visit(`/search?q=Reviews`);
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            REVIEWS_TABLE_NAME,
+            LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+          ],
+        });
+
+        cy.findByTestId("last_edited_at-search-filter").click();
+        popover().within(() => {
+          cy.findByText("Today").click();
+        });
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              collection: "Our analytics",
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+            },
+          ],
+          strict: false,
+        });
+      });
+
+      it("should remove last_edited_at filter when `X` is clicked on search filter", () => {
+        cy.visit(`/search?q=Reviews&last_edited_at=thisday`);
+        cy.wait("@search");
+
+        expectSearchResultContent({
+          expectedSearchResults: [
+            {
+              name: LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+              collection: "Our analytics",
+              timestamp: "Updated a few seconds ago by Robert Tableton",
+            },
+          ],
+          strict: false,
+        });
+
+        cy.findByTestId("last_edited_at-search-filter").within(() => {
+          cy.findByText("Today").should("exist");
+
+          cy.findByLabelText("close icon").click();
+
+          cy.findByText("Today").should("not.exist");
+          cy.findByText("Last edit date").should("exist");
+        });
+
+        cy.url().should("not.contain", "last_edited_at");
+
+        expectSearchResultItemNameContent({
+          itemNames: [
+            REVIEWS_TABLE_NAME,
+            LAST_EDITED_BY_NORMAL_USER_QUESTION.name,
+          ],
+        });
+      });
+    });
+
+    describeEE("verified filter", () => {
+      beforeEach(() => {
+        setTokenFeatures("all");
+        cy.createModerationReview({
+          status: "verified",
+          moderated_item_type: "card",
+          moderated_item_id: ORDERS_COUNT_QUESTION_ID,
+        });
+      });
+
+      it("should hydrate search with search text and verified filter", () => {
+        cy.visit("/search?q=orders&verified=true");
+        cy.wait("@search");
+
+        getSearchBar().should("have.value", "orders");
+
+        cy.findByTestId("search-app").within(() => {
+          cy.findByText('Results for "orders"').should("exist");
+        });
+
+        cy.findAllByTestId("search-result-item").each(result => {
+          cy.wrap(result).within(() => {
+            cy.findByLabelText("verified_filled icon").should("exist");
           });
+        });
+      });
+
+      it("should filter results by verified items", () => {
+        cy.visit("/");
+
+        getSearchBar().clear().type("e{enter}");
+        cy.wait("@search");
+
+        cy.findByTestId("verified-search-filter")
+          .findByText("Verified items only")
+          .click();
+
+        cy.wait("@search");
+
+        cy.findAllByTestId("search-result-item").each(result => {
+          cy.wrap(result).within(() => {
+            cy.findByLabelText("verified_filled icon").should("exist");
+          });
+        });
+      });
+
+      it("should not filter results when verified items is off", () => {
+        cy.visit("/search?q=e&verified=true");
+
+        cy.wait("@search");
 
-          cy.url().should("not.contain", "type");
+        cy.findByTestId("verified-search-filter")
+          .findByText("Verified items only")
+          .click();
+        cy.url().should("not.include", "verified=true");
 
-          // Check that we're getting elements other than Questions by checking the
-          // result text and checking if there's more than one result-link-info-text text
-          cy.findAllByTestId("result-link-info-text").then($elements => {
-            const textContent = new Set(
-              $elements.toArray().map(el => el.textContent),
-            );
-            expect(textContent.size).to.be.greaterThan(1);
+        let verifiedElementCount = 0;
+        let unverifiedElementCount = 0;
+        cy.findAllByTestId("search-result-item")
+          .each($el => {
+            if (!$el.find('[aria-label="verified_filled icon"]').length) {
+              unverifiedElementCount++;
+            } else {
+              verifiedElementCount++;
+            }
+          })
+          .then(() => {
+            expect(verifiedElementCount).to.eq(1);
+            expect(unverifiedElementCount).to.be.gt(0);
           });
+      });
+    });
+
+    describe("native query filter", () => {
+      beforeEach(() => {
+        cy.signInAsAdmin();
+        cy.createNativeQuestion({
+          name: TEST_NATIVE_QUESTION_NAME,
+          native: {
+            query: "SELECT 'reviews';",
+          },
+        });
+
+        cy.createNativeQuestion({
+          name: "Native Query",
+          native: {
+            query: `SELECT '${TEST_NATIVE_QUESTION_NAME}';`,
+          },
+        });
+      });
+
+      it("should hydrate search with search text and native query filter", () => {
+        cy.visit(
+          `/search?q=${TEST_NATIVE_QUESTION_NAME}&search_native_query=true`,
+        );
+        cy.wait("@search");
+
+        getSearchBar().should("have.value", TEST_NATIVE_QUESTION_NAME);
+
+        cy.findByTestId("search-app").within(() => {
+          cy.findByText(`Results for "${TEST_NATIVE_QUESTION_NAME}"`).should(
+            "exist",
+          );
+        });
+
+        expectSearchResultItemNameContent({
+          itemNames: [TEST_NATIVE_QUESTION_NAME, "Native Query"],
+        });
+      });
+
+      it("should include results that contain native query data when the toggle is on", () => {
+        cy.visit(`/search?q=${TEST_NATIVE_QUESTION_NAME}`);
+        cy.wait("@search");
+
+        expectSearchResultItemNameContent({
+          itemNames: [TEST_NATIVE_QUESTION_NAME],
+        });
+
+        cy.findByTestId("search_native_query-search-filter")
+          .findByText("Search the contents of native queries")
+          .click();
+
+        cy.url().should("include", "search_native_query=true");
+
+        expectSearchResultItemNameContent({
+          itemNames: [TEST_NATIVE_QUESTION_NAME, "Native Query"],
+        });
+      });
+
+      it("should not include results that contain native query data if the toggle is off", () => {
+        cy.visit(
+          `/search?q=${TEST_NATIVE_QUESTION_NAME}&search_native_query=true`,
+        );
+        cy.wait("@search");
+
+        expectSearchResultItemNameContent({
+          itemNames: [TEST_NATIVE_QUESTION_NAME, "Native Query"],
+        });
+
+        cy.findByTestId("search_native_query-search-filter")
+          .findByText("Search the contents of native queries")
+          .click();
+
+        expectSearchResultItemNameContent({
+          itemNames: [TEST_NATIVE_QUESTION_NAME],
         });
       });
     });
+
+    it("should persist filters when the user changes the text query", () => {
+      cy.visit("/search?q=orders");
+
+      // add created_by filter
+      cy.findByTestId("created_by-search-filter").click();
+      popover().within(() => {
+        cy.findByText("Bobby Tables").click();
+        cy.findByText("Apply").click();
+      });
+
+      // add last_edited_by filter
+      cy.findByTestId("last_edited_by-search-filter").click();
+      popover().within(() => {
+        cy.findByText("Bobby Tables").click();
+        cy.findByText("Apply").click();
+      });
+
+      // add type filter
+      cy.findByTestId("type-search-filter").click();
+      popover().within(() => {
+        cy.findByText("Question").click();
+        cy.findByText("Apply").click();
+      });
+
+      expectSearchResultItemNameContent({
+        itemNames: [
+          "Orders",
+          "Orders, Count",
+          "Orders, Count, Grouped by Created At (year)",
+        ],
+      });
+
+      getSearchBar().clear().type("count{enter}");
+
+      expectSearchResultItemNameContent({
+        itemNames: [
+          "Orders, Count",
+          "Orders, Count, Grouped by Created At (year)",
+        ],
+      });
+    });
   });
 });
 
@@ -294,14 +1096,77 @@ describeWithSnowplow("scenarios > search", () => {
   });
 });
 
-function getProductsSearchResults() {
-  cy.findByText("Products");
-  // This part about the description reproduces metabase#20018
-  cy.findByText(
-    "Includes a catalog of all the products ever sold by the famed Sample Company.",
-  );
-}
-
 function getSearchBar() {
   return cy.findByPlaceholderText("Search…");
 }
+
+function expectSearchResultItemNameContent(
+  { itemNames },
+  { strict } = { strict: true },
+) {
+  cy.findAllByTestId("search-result-item-name").then($searchResultLabel => {
+    const searchResultLabelList = $searchResultLabel
+      .toArray()
+      .map(el => el.textContent);
+
+    if (strict) {
+      expect(searchResultLabelList).to.have.length(itemNames.length);
+    }
+    expect(searchResultLabelList).to.include.members(itemNames);
+  });
+}
+
+/**
+ * Checks the search results against expectedSearchValues, including descriptions,
+ * collection names, and timestamps, depending on the given data.
+ *
+ * @param {Object} options - Options for the test.
+ * @param {Object[]} options.expectedSearchResults - An array of search result items to compare against.
+ * @param {string} options.expectedSearchResults[].name - The name of the search result item.
+ * @param {string} options.expectedSearchResults[].description - The description of the search result item.
+ * @param {string} options.expectedSearchResults[].collection - The collection label of the search result item.
+ * @param {string} options.expectedSearchResults[].timestamp - The timestamp label of the search result item .
+ * @param {boolean} [strict=true] - Whether to check if the contents AND length of search results are the same
+ */
+function expectSearchResultContent({ expectedSearchResults, strict = true }) {
+  const searchResultItemSelector = "[data-testid=search-result-item]";
+
+  const searchResultItems = cy.get(searchResultItemSelector);
+
+  searchResultItems.then($results => {
+    if (strict) {
+      // Check if the length of the search results is the same as the expected length
+      expect($results).to.have.length(expectedSearchResults.length);
+    }
+  });
+
+  for (const expectedSearchResult of expectedSearchResults) {
+    cy.contains(searchResultItemSelector, expectedSearchResult.name).within(
+      () => {
+        cy.findByTestId("search-result-item-name").should(
+          "have.text",
+          expectedSearchResult.name,
+        );
+
+        if (expectedSearchResult.description) {
+          cy.findByTestId("result-description").should(
+            "have.text",
+            expectedSearchResult.description,
+          );
+        }
+
+        if (expectedSearchResult.collection) {
+          cy.findAllByTestId("result-link-wrapper").first(() => {
+            cy.findByText(expectedSearchResult.collection).should("exist");
+          });
+        }
+        if (expectedSearchResult.timestamp) {
+          cy.findByTestId("revision-history-button").should(
+            "have.text",
+            expectedSearchResult.timestamp,
+          );
+        }
+      },
+    );
+  }
+}
diff --git a/e2e/test/scenarios/organization/content-verification.cy.spec.js b/e2e/test/scenarios/organization/content-verification.cy.spec.js
index c4c9de8077d03fe6be97d5b33892c8b687634cf2..c2372a48272efea59a87e0ea2984294d7dc9f884 100644
--- a/e2e/test/scenarios/organization/content-verification.cy.spec.js
+++ b/e2e/test/scenarios/organization/content-verification.cy.spec.js
@@ -180,7 +180,7 @@ describeEE("scenarios > premium > content verification", () => {
           .first()
           .within(() => {
             cy.findByText("Orders, Count");
-            cy.icon("verified");
+            cy.icon("verified_filled");
           });
 
         cy.visit("/collection/root");
diff --git a/e2e/test/scenarios/organization/official-collections.cy.spec.js b/e2e/test/scenarios/organization/official-collections.cy.spec.js
index 9a8b6f3d1e6fac711acb079a77205fa60b7cfe88..ea3773e38c2fa9bd79fd912d65841fa0cfa8e5a4 100644
--- a/e2e/test/scenarios/organization/official-collections.cy.spec.js
+++ b/e2e/test/scenarios/organization/official-collections.cy.spec.js
@@ -199,7 +199,7 @@ function testOfficialBadgeInSearch({
   cy.findByTestId("search-results-list").within(() => {
     assertSearchResultBadge(collection, {
       expectBadge,
-      selector: "h3",
+      selector: "[data-testid='search-result-item-name']",
     });
     assertSearchResultBadge(question, { expectBadge });
     assertSearchResultBadge(dashboard, { expectBadge });
@@ -283,7 +283,8 @@ function assertSearchResultBadge(itemName, opts) {
   const { expectBadge } = opts;
   cy.findByText(itemName, opts)
     .parentsUntil("[data-testid=search-result-item]")
-    .last()
+    .parent()
+    .first()
     .within(() => {
       cy.icon("badge").should(expectBadge ? "exist" : "not.exist");
     });
diff --git a/e2e/test/scenarios/question/new.cy.spec.js b/e2e/test/scenarios/question/new.cy.spec.js
index e6c9bb7c6de44549f2292f0479f34ace02db7ba9..790a5bd397c17906d2aa2d8d8fb3117f8956a3ab 100644
--- a/e2e/test/scenarios/question/new.cy.spec.js
+++ b/e2e/test/scenarios/question/new.cy.spec.js
@@ -72,13 +72,13 @@ describe("scenarios > question > new", () => {
       );
 
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-      cy.contains("Saved question in Our analytics");
+      cy.contains("Our analytics");
       cy.findAllByRole("link", { name: "Our analytics" })
         .should("have.attr", "href")
         .and("eq", "/collection/root");
 
       // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-      cy.contains("Table in Sample Database");
+      cy.contains("Sample Database");
       cy.findAllByRole("link", { name: "Sample Database" })
         .should("have.attr", "href")
         .and("eq", `/browse/${SAMPLE_DB_ID}-sample-database`);
diff --git a/e2e/test/scenarios/question/reproductions/19341-disabled-nested-queries.cy.spec.js b/e2e/test/scenarios/question/reproductions/19341-disabled-nested-queries.cy.spec.js
index 415e3679242b88cc7ca75c0aa010ebb75acdaf40..5cd73eff3444fe7b8a52ad7a68a9dac84772c388 100644
--- a/e2e/test/scenarios/question/reproductions/19341-disabled-nested-queries.cy.spec.js
+++ b/e2e/test/scenarios/question/reproductions/19341-disabled-nested-queries.cy.spec.js
@@ -38,8 +38,17 @@ describe("issue 19341", () => {
       // Ensure the search doesn't list saved questions
       cy.findByPlaceholderText("Search for a table…").type("Ord");
       cy.findByText("Loading...").should("not.exist");
-      cy.findAllByText(/Saved question in/i).should("not.exist");
-      cy.findAllByText(/Table in/i).should("exist");
+
+      cy.findAllByTestId("search-result-item").then($result => {
+        const searchResults = $result.toArray();
+        const modelTypes = new Set(
+          searchResults.map(k => k.getAttribute("data-model-type")),
+        );
+
+        expect(modelTypes).not.to.include("card");
+        expect(modelTypes).to.include("table");
+      });
+
       cy.icon("close").click();
 
       cy.findByText("Sample Database").click();
diff --git a/enterprise/backend/test/metabase_enterprise/audit_db_test.clj b/enterprise/backend/test/metabase_enterprise/audit_db_test.clj
index f27210393d9bf55ba7693a8647e0a2bba8f79601..d953707735f193ea25e2f04e2418b2ec2ef0bd7a 100644
--- a/enterprise/backend/test/metabase_enterprise/audit_db_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/audit_db_test.clj
@@ -42,11 +42,12 @@
 (deftest audit-db-content-is-installed-when-found
   (mt/test-drivers #{:postgres}
     (with-audit-db-restoration
-      (with-redefs [audit-db/analytics-root-dir-resource (io/resource "instance_analytics_skip")]
-        (is (str/ends-with? (str audit-db/analytics-root-dir-resource)
-                            "instance_analytics_skip"))
-        (is (= :metabase-enterprise.audit-db/installed (audit-db/ensure-audit-db-installed!)))
-        (is (= 13371337 (t2/select-one-fn :id 'Database {:where [:= :is_audit true]}))
-            "Audit DB is installed.")
-        (is (not= 0 (t2/count 'Card {:where [:= :database_id 13371337]}))
-            "Cards should be created for Audit DB when the content is there.")))))
+      (mt/with-model-cleanup [:model/Dashboard :model/Card]
+        (with-redefs [audit-db/analytics-root-dir-resource (io/resource "instance_analytics_skip")]
+          (is (str/ends-with? (str audit-db/analytics-root-dir-resource)
+                              "instance_analytics_skip"))
+          (is (= :metabase-enterprise.audit-db/installed (audit-db/ensure-audit-db-installed!)))
+          (is (= 13371337 (t2/select-one-fn :id 'Database {:where [:= :is_audit true]}))
+              "Audit DB is installed.")
+          (is (not= 0 (t2/count 'Card {:where [:= :database_id 13371337]}))
+              "Cards should be created for Audit DB when the content is there."))))))
diff --git a/enterprise/frontend/src/metabase-enterprise/collections/components/CollectionAuthorityLevelIcon.tsx b/enterprise/frontend/src/metabase-enterprise/collections/components/CollectionAuthorityLevelIcon.tsx
index 9043899fbb6ac6988b5bbc2e89425bfd1d82820f..a3c8617803e88e55afd83a6fe066dac7f5993527 100644
--- a/enterprise/frontend/src/metabase-enterprise/collections/components/CollectionAuthorityLevelIcon.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/collections/components/CollectionAuthorityLevelIcon.tsx
@@ -1,37 +1,25 @@
-import type { IconProps } from "metabase/core/components/Icon";
+/* eslint-disable react/prop-types */
 import { Icon } from "metabase/core/components/Icon";
 
 import { color } from "metabase/lib/colors";
 
-import type { Collection } from "metabase-types/api";
-
+import type { CollectionAuthorityLevelIcon as CollectionAuthorityLevelIconComponent } from "metabase/plugins/index";
 import { AUTHORITY_LEVELS } from "../constants";
 import { isRegularCollection } from "../utils";
 
-interface Props extends Omit<IconProps, "name" | "tooltip"> {
-  collection: Collection;
-
-  // check OFFICIAL_COLLECTION authority level definition
-  // https://github.com/metabase/metabase/blob/d0ab6c0e2361dccfbfe961d61e1066ec2faf6c40/enterprise/frontend/src/metabase-enterprise/collections/constants.js#L14
-  tooltip?: "default" | "belonging";
-}
-
-export function CollectionAuthorityLevelIcon({
-  collection,
-  tooltip = "default",
-  ...iconProps
-}: Props) {
-  if (isRegularCollection(collection)) {
-    return null;
-  }
-  const level = AUTHORITY_LEVELS[String(collection.authority_level)];
-  return (
-    <Icon
-      {...iconProps}
-      name={level.icon}
-      tooltip={level.tooltips?.[tooltip] || tooltip}
-      style={{ color: level.color ? color(level.color) : undefined }}
-      data-testid={`${level.type}-collection-marker`}
-    />
-  );
-}
+export const CollectionAuthorityLevelIcon: CollectionAuthorityLevelIconComponent =
+  ({ collection, tooltip = "default", ...iconProps }) => {
+    if (isRegularCollection(collection)) {
+      return null;
+    }
+    const level = AUTHORITY_LEVELS[String(collection.authority_level)];
+    return (
+      <Icon
+        {...iconProps}
+        name={level.icon}
+        tooltip={level.tooltips?.[tooltip] || tooltip}
+        style={{ color: level.color ? color(level.color) : undefined }}
+        data-testid={`${level.type}-collection-marker`}
+      />
+    );
+  };
diff --git a/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/VerifiedFilter.tsx b/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/VerifiedFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7e026d49c5e3473e65a6ca5605cc5eacb6305930
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/VerifiedFilter.tsx
@@ -0,0 +1,9 @@
+import { t } from "ttag";
+import type { SearchFilterComponent } from "metabase/search/types";
+
+export const VerifiedFilter: SearchFilterComponent<"verified"> = {
+  label: () => t`Verified items only`,
+  type: "toggle",
+  fromUrl: value => value === "true",
+  toUrl: (value: boolean) => (value ? "true" : null),
+};
diff --git a/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/VerifiedFilter.unit.spec.tsx b/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/VerifiedFilter.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ff5c5e4352079710809178e8d37898e482f0ea0
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/VerifiedFilter.unit.spec.tsx
@@ -0,0 +1,42 @@
+import { VerifiedFilter } from "./VerifiedFilter";
+
+const fromUrl = VerifiedFilter.fromUrl;
+const toUrl = VerifiedFilter.toUrl;
+
+describe("fromUrl", () => {
+  it('should convert "true" string to boolean true', () => {
+    const value = "true";
+    const result = fromUrl(value);
+    expect(result).toBe(true);
+  });
+
+  it("should convert any other string to boolean false", () => {
+    const falseValue = fromUrl("false");
+    const invalidValue = fromUrl("invalid");
+
+    expect(falseValue).toBe(false);
+    expect(invalidValue).toBe(false);
+  });
+
+  it("should return null when value is null or undefined", () => {
+    const nullValue = fromUrl(null);
+    const undefinedValue = fromUrl(undefined);
+
+    expect(nullValue).toBe(false);
+    expect(undefinedValue).toBe(false);
+  });
+});
+
+describe("toUrl", () => {
+  it('should convert boolean true to "true" string', () => {
+    const value = true;
+    const result = toUrl(value);
+    expect(result).toBe("true");
+  });
+
+  it("should convert boolean false to null", () => {
+    const value = false;
+    const result = toUrl(value);
+    expect(result).toBeNull();
+  });
+});
diff --git a/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/index.ts b/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..96951fe59ba5e2374de66019e3ffe3bc9ce04040
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/content_verification/VerifiedFilter/index.ts
@@ -0,0 +1 @@
+export * from "./VerifiedFilter";
diff --git a/enterprise/frontend/src/metabase-enterprise/content_verification/index.ts b/enterprise/frontend/src/metabase-enterprise/content_verification/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f560bc5057963d8320d829536835e6b957110d3a
--- /dev/null
+++ b/enterprise/frontend/src/metabase-enterprise/content_verification/index.ts
@@ -0,0 +1,7 @@
+import { PLUGIN_CONTENT_VERIFICATION } from "metabase/plugins";
+import { hasPremiumFeature } from "metabase-enterprise/settings";
+import { VerifiedFilter } from "metabase-enterprise/content_verification/VerifiedFilter";
+
+if (hasPremiumFeature("content_verification")) {
+  PLUGIN_CONTENT_VERIFICATION.VerifiedFilter = VerifiedFilter;
+}
diff --git a/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationStatusIcon/ModerationStatusIcon.tsx b/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationStatusIcon/ModerationStatusIcon.tsx
index a48fe06dd2d6be025060f074b99629cc4af6e849..78445aedcb9d0a0dfe6b4bede24cfac7343879c9 100644
--- a/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationStatusIcon/ModerationStatusIcon.tsx
+++ b/enterprise/frontend/src/metabase-enterprise/moderation/components/ModerationStatusIcon/ModerationStatusIcon.tsx
@@ -6,13 +6,15 @@ import { Icon } from "metabase/core/components/Icon";
 
 type ModerationStatusIconProps = {
   status: string | null | undefined;
+  filled?: boolean;
 } & Partial<IconProps>;
 
 export const ModerationStatusIcon = ({
   status,
+  filled = false,
   ...iconProps
 }: ModerationStatusIconProps) => {
-  const { name: iconName, color: iconColor } = getStatusIcon(status);
+  const { name: iconName, color: iconColor } = getStatusIcon(status, filled);
   return iconName ? (
     <Icon name={iconName} color={color(iconColor)} {...iconProps} />
   ) : null;
diff --git a/enterprise/frontend/src/metabase-enterprise/moderation/constants.js b/enterprise/frontend/src/metabase-enterprise/moderation/constants.js
index 3e4417c28b91e17c8ab43324aa5510ead9c447cc..4b56960553e9daf94177fafa05386c7b25798d0b 100644
--- a/enterprise/frontend/src/metabase-enterprise/moderation/constants.js
+++ b/enterprise/frontend/src/metabase-enterprise/moderation/constants.js
@@ -7,6 +7,10 @@ export const MODERATION_STATUS_ICONS = {
     name: "verified",
     color: "brand",
   },
+  verified_filled: {
+    name: "verified_filled",
+    color: "brand",
+  },
   null: {
     name: "close",
     color: "text-light",
diff --git a/enterprise/frontend/src/metabase-enterprise/moderation/service.js b/enterprise/frontend/src/metabase-enterprise/moderation/service.js
index 1bf8002cc23d09261be1dbbb17528ce2cb4d580d..87d76b795b418a041f0515cdf8e84a852353cd52 100644
--- a/enterprise/frontend/src/metabase-enterprise/moderation/service.js
+++ b/enterprise/frontend/src/metabase-enterprise/moderation/service.js
@@ -24,11 +24,16 @@ export function removeReview({ itemId, itemType }) {
 }
 
 const noIcon = {};
-export function getStatusIcon(status) {
+
+export function getStatusIcon(status, filled = false) {
   if (isRemovedReviewStatus(status)) {
     return noIcon;
   }
 
+  if (status === "verified" && filled) {
+    return MODERATION_STATUS_ICONS[`${status}_filled`];
+  }
+
   return MODERATION_STATUS_ICONS[status] || noIcon;
 }
 
diff --git a/enterprise/frontend/src/metabase-enterprise/plugins.js b/enterprise/frontend/src/metabase-enterprise/plugins.js
index e1f989ce08a80129faa38c8de1eeb5a364510bdc..35e1ff822b83e111affc33b1c3a1ee54b726cb5c 100644
--- a/enterprise/frontend/src/metabase-enterprise/plugins.js
+++ b/enterprise/frontend/src/metabase-enterprise/plugins.js
@@ -16,6 +16,7 @@ import "./sandboxes";
 import "./auth";
 import "./caching";
 import "./collections";
+import "./content_verification";
 import "./whitelabel";
 import "./embedding";
 import "./snippets";
diff --git a/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx b/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx
index c62bd7eba0ea7ee740c168b1dc1d169c86abc94f..28d6e01fabf4a06bf8f663e398479764a467f6e8 100644
--- a/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx
+++ b/enterprise/frontend/src/metabase-enterprise/snippets/components/CollectionOptionsButton.jsx
@@ -34,7 +34,7 @@ export default class CollectionOptionsButton extends Component {
               className="text-brand"
               sections={[{ items }]}
               onChange={item => {
-                item.onClick();
+                item.onClick(item);
                 closePopover();
               }}
             />
diff --git a/frontend/src/metabase-types/api/activity.ts b/frontend/src/metabase-types/api/activity.ts
index b1909254dddeb4fb78d7d78286031494c5f48a5e..c2e2ba914e96453f80f0d55d54fe5f071bc0865c 100644
--- a/frontend/src/metabase-types/api/activity.ts
+++ b/frontend/src/metabase-types/api/activity.ts
@@ -1,6 +1,8 @@
 export type ModelType = "table" | "card" | "dataset" | "dashboard";
 
 export interface ModelObject {
+  display_name?: string;
+  moderated_status?: string;
   name: string;
 }
 
diff --git a/frontend/src/metabase-types/api/mocks/search.ts b/frontend/src/metabase-types/api/mocks/search.ts
index dc540247a9f8788b820ef98e3635959d220c94ba..77ac0e539645762ff8af93af2f686cb17b5e9e87 100644
--- a/frontend/src/metabase-types/api/mocks/search.ts
+++ b/frontend/src/metabase-types/api/mocks/search.ts
@@ -36,6 +36,12 @@ export const createMockSearchResult = (
     dashboard_count: null,
     context: null,
     scores: [createMockSearchScore()],
+    created_at: "2022-01-01T00:00:00.000Z",
+    creator_common_name: "Testy Tableton",
+    creator_id: 2,
+    last_edited_at: "2023-01-01T00:00:00.000Z",
+    last_editor_common_name: "Bobby Tables",
+    last_editor_id: 1,
     ...options,
   };
 };
diff --git a/frontend/src/metabase-types/api/mocks/user.ts b/frontend/src/metabase-types/api/mocks/user.ts
index 8cb0361bb6f981530f76e88016412076e57b86c5..f53b4ad47dcf0de07bff855f6c96861004abed88 100644
--- a/frontend/src/metabase-types/api/mocks/user.ts
+++ b/frontend/src/metabase-types/api/mocks/user.ts
@@ -23,7 +23,7 @@ export const createMockUser = (opts?: Partial<User>): User => ({
   ...opts,
 });
 
-export const createMockerUserListResult = (
+export const createMockUserListResult = (
   opts?: Partial<UserListResult>,
 ): UserListResult => ({
   id: 1,
diff --git a/frontend/src/metabase-types/api/search.ts b/frontend/src/metabase-types/api/search.ts
index ec1d5c38312d00b41ba180fb15692540c93aaf49..b1a45f0e2cb6ccdd98737906b4b8a8e7134a70ff 100644
--- a/frontend/src/metabase-types/api/search.ts
+++ b/frontend/src/metabase-types/api/search.ts
@@ -1,3 +1,4 @@
+import type { UserId } from "metabase-types/api/user";
 import type { CardId } from "./card";
 import type { Collection } from "./collection";
 import type { DatabaseId, InitialSyncStatus } from "./database";
@@ -71,6 +72,12 @@ export interface SearchResult {
   dashboard_count: number | null;
   context: any; // this might be a dead property
   scores: SearchScore[];
+  last_edited_at: string | null;
+  last_editor_id: UserId | null;
+  last_editor_common_name: string | null;
+  creator_id: UserId | null;
+  creator_common_name: string | null;
+  created_at: string | null;
 }
 
 export interface SearchListQuery {
diff --git a/frontend/src/metabase/components/EventSandbox/EventSandbox.tsx b/frontend/src/metabase/components/EventSandbox/EventSandbox.tsx
index 7f1b5e5165a5710c897e26a29b91da759f8dea7b..b9336ed760291202ba5b386950ab0fee7cca3e2a 100644
--- a/frontend/src/metabase/components/EventSandbox/EventSandbox.tsx
+++ b/frontend/src/metabase/components/EventSandbox/EventSandbox.tsx
@@ -20,6 +20,7 @@ type EventSandboxProps = {
   enableMouseEvents?: boolean;
   disabled?: boolean;
   preventDefault?: boolean;
+  className?: string;
 };
 
 // Prevent DOM events from bubbling through the React component tree
@@ -30,6 +31,7 @@ function EventSandbox({
   disabled,
   enableMouseEvents = false,
   preventDefault = false,
+  className,
 }: EventSandboxProps) {
   const stop = useCallback(
     (event: React.SyntheticEvent) => {
@@ -56,6 +58,7 @@ function EventSandbox({
     <React.Fragment>{children}</React.Fragment>
   ) : (
     <div
+      className={className}
       onClick={stop}
       onContextMenu={stop}
       onDoubleClick={stop}
diff --git a/frontend/src/metabase/components/LastEditInfoLabel/LastEditInfoLabel.jsx b/frontend/src/metabase/components/LastEditInfoLabel/LastEditInfoLabel.jsx
index 6c1929df9b88a72461af04fca5871db80631d7cd..0f79e372826314434364a6297bf3e3139ad50943 100644
--- a/frontend/src/metabase/components/LastEditInfoLabel/LastEditInfoLabel.jsx
+++ b/frontend/src/metabase/components/LastEditInfoLabel/LastEditInfoLabel.jsx
@@ -18,13 +18,14 @@ function mapStateToProps(state) {
 LastEditInfoLabel.propTypes = {
   item: PropTypes.shape({
     "last-edit-info": PropTypes.shape({
-      id: PropTypes.number.isRequired,
-      email: PropTypes.string.isRequired,
+      id: PropTypes.number,
+      email: PropTypes.string,
       first_name: PropTypes.string,
       last_name: PropTypes.string,
-      timestamp: PropTypes.string.isRequired,
+      timestamp: PropTypes.string,
     }).isRequired,
   }),
+  prefix: PropTypes.string,
   user: PropTypes.shape({
     id: PropTypes.number,
   }).isRequired,
@@ -38,23 +39,43 @@ function formatEditorName(lastEditInfo) {
   return name || lastEditInfo.email;
 }
 
-function LastEditInfoLabel({ item, user, onClick, className }) {
+function LastEditInfoLabel({
+  item,
+  user,
+  prefix = t`Edited`,
+  onClick,
+  className,
+}) {
   const lastEditInfo = item["last-edit-info"];
   const { id: editorId, timestamp } = lastEditInfo;
-  const time = moment(timestamp).fromNow();
+
+  const momentTimestamp = moment(timestamp);
+  const timeLabel =
+    timestamp && momentTimestamp.isValid() ? momentTimestamp.fromNow() : null;
 
   const editor = editorId === user.id ? t`you` : formatEditorName(lastEditInfo);
+  const editorLabel = editor ? t`by ${editor}` : null;
+
+  const label =
+    timeLabel || editorLabel
+      ? [timeLabel, editorLabel].filter(Boolean).join(" ")
+      : null;
 
-  return (
-    <Tooltip tooltip={<DateTime value={timestamp} />}>
+  return label ? (
+    <Tooltip
+      tooltip={timestamp ? <DateTime value={timestamp} /> : null}
+      isEnabled={!!timeLabel}
+    >
       <TextButton
         size="small"
         className={className}
         onClick={onClick}
         data-testid="revision-history-button"
-      >{t`Edited ${time} by ${editor}`}</TextButton>
+      >
+        {prefix} {label}
+      </TextButton>
     </Tooltip>
-  );
+  ) : null;
 }
 
 export default connect(mapStateToProps)(LastEditInfoLabel);
diff --git a/frontend/src/metabase/containers/DataPicker/tests/common.tsx b/frontend/src/metabase/containers/DataPicker/tests/common.tsx
index 23f30cd6b62eb856587ebfcee2e073d9c1de3ee6..76a93b95efe127d80b85844360ef2373a5af09ee 100644
--- a/frontend/src/metabase/containers/DataPicker/tests/common.tsx
+++ b/frontend/src/metabase/containers/DataPicker/tests/common.tsx
@@ -4,6 +4,8 @@ import {
   setupCollectionsEndpoints,
   setupDatabasesEndpoints,
   setupSearchEndpoints,
+  setupUsersEndpoints,
+  setupCollectionByIdEndpoint,
 } from "__support__/server-mocks";
 import { renderWithProviders, waitForLoaderToBeRemoved } from "__support__/ui";
 
@@ -16,6 +18,7 @@ import {
   createMockCollectionItem,
   createMockDatabase,
   createMockTable,
+  createMockUser,
 } from "metabase-types/api/mocks";
 import { createMockSettingsState } from "metabase-types/store/mocks";
 
@@ -211,8 +214,13 @@ export async function setup({
     setupDatabasesEndpoints([], { hasSavedQuestions: false });
   }
 
+  const collectionList = [SAMPLE_COLLECTION, EMPTY_COLLECTION];
   setupCollectionsEndpoints({
-    collections: [SAMPLE_COLLECTION, EMPTY_COLLECTION],
+    collections: collectionList,
+  });
+
+  setupCollectionByIdEndpoint({
+    collections: collectionList,
   });
 
   setupCollectionVirtualSchemaEndpoints(createMockCollection(ROOT_COLLECTION), [
@@ -240,6 +248,8 @@ export async function setup({
     setupSearchEndpoints([SAMPLE_QUESTION_SEARCH_ITEM]);
   }
 
+  setupUsersEndpoints([createMockUser()]);
+
   const settings = createMockSettingsState({
     "enable-nested-queries": hasNestedQueriesEnabled,
   });
diff --git a/frontend/src/metabase/lib/urls/browse.ts b/frontend/src/metabase/lib/urls/browse.ts
index 0ac4b8229724ea14290dfa534e71090f73120bd7..664e4d35dbf95dbd86ecce404a32f3a37de3195d 100644
--- a/frontend/src/metabase/lib/urls/browse.ts
+++ b/frontend/src/metabase/lib/urls/browse.ts
@@ -15,7 +15,11 @@ export function browseDatabase(database: Database) {
   return appendSlug(`/browse/${database.id}`, slugg(name));
 }
 
-export function browseSchema(table: Table) {
+export function browseSchema(table: {
+  db_id?: Table["db_id"];
+  schema_name: Table["schema_name"] | null;
+  db?: Pick<Database, "id">;
+}) {
   const databaseId = table.db?.id || table.db_id;
   return `/browse/${databaseId}/schema/${encodeURIComponent(
     table.schema_name ?? "",
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.jsx b/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.jsx
deleted file mode 100644
index 0c6e3900366f52979b1b74288954b2f737212f60..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.jsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import { Fragment, useState, useEffect } from "react";
-import PropTypes from "prop-types";
-import { t } from "ttag";
-import _ from "underscore";
-import { connect } from "react-redux";
-import { push } from "react-router-redux";
-
-import RecentItems from "metabase/entities/recent-items";
-import Text from "metabase/components/type/Text";
-import * as Urls from "metabase/lib/urls";
-import { isSyncCompleted } from "metabase/lib/syncing";
-import { PLUGIN_MODERATION } from "metabase/plugins";
-import {
-  ResultLink,
-  ResultButton,
-  ResultSpinner,
-  Title,
-  TitleWrapper,
-  ItemIcon,
-} from "metabase/search/components/SearchResult";
-import EmptyState from "metabase/components/EmptyState";
-import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
-import { useListKeyboardNavigation } from "metabase/hooks/use-list-keyboard-navigation";
-
-import { getTranslatedEntityName } from "metabase/common/utils/model-names";
-import {
-  Root,
-  EmptyStateContainer,
-  Header,
-  RecentListItemContent,
-} from "./RecentsList.styled";
-
-const LOADER_THRESHOLD = 100;
-
-const propTypes = {
-  list: PropTypes.arrayOf(
-    PropTypes.shape({
-      model_id: PropTypes.number,
-      model: PropTypes.string,
-      model_object: PropTypes.object,
-    }),
-  ),
-  loading: PropTypes.bool,
-  onChangeLocation: PropTypes.func,
-  onClick: PropTypes.func,
-  className: PropTypes.string,
-};
-
-const getItemUrl = item => (isItemActive(item) ? Urls.modelToUrl(item) : "");
-
-function RecentsList({ list, loading, onChangeLocation, onClick, className }) {
-  const handleSelectItem = item => {
-    onClick?.({
-      ...item.model_object,
-      model: item.model,
-      name: item.model_object.display_name ?? item.model_object.name,
-    });
-  };
-
-  const { getRef, cursorIndex } = useListKeyboardNavigation({
-    list,
-    onEnter: item =>
-      onClick ? handleSelectItem(item) : onChangeLocation(getItemUrl(item)),
-  });
-
-  const [canShowLoader, setCanShowLoader] = useState(false);
-  const hasRecents = list?.length > 0;
-
-  useEffect(() => {
-    const timer = setTimeout(() => setCanShowLoader(true), LOADER_THRESHOLD);
-    return () => clearTimeout(timer);
-  }, []);
-
-  if (loading && !canShowLoader) {
-    return null;
-  }
-
-  // we want to remove link behavior if we have an onClick handler
-  const ResultContainer = onClick ? ResultButton : ResultLink;
-
-  return (
-    <Root className={className}>
-      <Header>{t`Recently viewed`}</Header>
-      <LoadingAndErrorWrapper loading={loading} noWrapper>
-        <Fragment>
-          {hasRecents && (
-            <ul>
-              {list.map((item, index) => {
-                const key = getItemKey(item);
-                const title = getItemName(item);
-                const type = getTranslatedEntityName(item.model);
-                const active = isItemActive(item);
-                const loading = isItemLoading(item);
-                const url = getItemUrl(item);
-                const moderatedStatus = getModeratedStatus(item);
-
-                return (
-                  <li key={key} ref={getRef(item)}>
-                    <ResultContainer
-                      isSelected={cursorIndex === index}
-                      active={active}
-                      compact={true}
-                      to={onClick ? undefined : url}
-                      onClick={
-                        onClick ? () => handleSelectItem(item) : undefined
-                      }
-                    >
-                      <RecentListItemContent
-                        align="start"
-                        data-testid="recently-viewed-item"
-                      >
-                        <ItemIcon
-                          item={item}
-                          type={item.model}
-                          active={active}
-                        />
-                        <div>
-                          <TitleWrapper>
-                            <Title
-                              active={active}
-                              data-testid="recently-viewed-item-title"
-                            >
-                              {title}
-                            </Title>
-                            <PLUGIN_MODERATION.ModerationStatusIcon
-                              status={moderatedStatus}
-                              size={12}
-                            />
-                          </TitleWrapper>
-                          <Text data-testid="recently-viewed-item-type">
-                            {type}
-                          </Text>
-                        </div>
-                        {loading && <ResultSpinner size={24} borderWidth={3} />}
-                      </RecentListItemContent>
-                    </ResultContainer>
-                  </li>
-                );
-              })}
-            </ul>
-          )}
-
-          {!hasRecents && (
-            <EmptyStateContainer>
-              <EmptyState message={t`Nothing here`} icon="folder" />
-            </EmptyStateContainer>
-          )}
-        </Fragment>
-      </LoadingAndErrorWrapper>
-    </Root>
-  );
-}
-
-RecentsList.propTypes = propTypes;
-
-const getItemKey = ({ model, model_id }) => {
-  return `${model}:${model_id}`;
-};
-
-const getItemName = ({ model_object }) => {
-  return model_object.display_name || model_object.name;
-};
-
-const getModeratedStatus = ({ model_object }) => {
-  return model_object.moderated_status;
-};
-
-const isItemActive = ({ model, model_object }) => {
-  switch (model) {
-    case "table":
-      return isSyncCompleted(model_object);
-    default:
-      return true;
-  }
-};
-
-const isItemLoading = ({ model, model_object }) => {
-  switch (model) {
-    case "database":
-    case "table":
-      return !isSyncCompleted(model_object);
-    default:
-      return false;
-  }
-};
-
-export default _.compose(
-  connect(null, {
-    onChangeLocation: push,
-  }),
-  RecentItems.loadList({
-    wrapped: true,
-    reload: true,
-    loadingAndErrorWrapper: false,
-  }),
-)(RecentsList);
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.styled.tsx b/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.styled.tsx
deleted file mode 100644
index 3b403f00263c3e930bd06ef8be8fbc9718e699f3..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.styled.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import styled from "@emotion/styled";
-
-import { color } from "metabase/lib/colors";
-import { breakpointMinSmall } from "metabase/styled-components/theme";
-
-export const Root = styled.div`
-  padding-top: 0.5rem;
-  padding-bottom: 0.5rem;
-
-  background-color: ${color("bg-white")};
-  line-height: 24px;
-
-  box-shadow: 0 20px 20px ${color("shadow")};
-
-  ${breakpointMinSmall} {
-    border: 1px solid ${color("border")};
-    border-radius: 6px;
-    box-shadow: 0 7px 20px ${color("shadow")};
-  }
-`;
-
-export const EmptyStateContainer = styled.div`
-  margin: 3rem 0;
-`;
-
-export const Header = styled.h4`
-  padding: 0.5rem 1rem;
-`;
-
-export const RecentListItemContent = styled.div`
-  display: flex;
-  align-items: flex-start;
-`;
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.tsx b/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0c98f74de28e0916eda868c952f7b9850ccdf013
--- /dev/null
+++ b/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.tsx
@@ -0,0 +1,70 @@
+import { useMemo } from "react";
+import { push } from "react-router-redux";
+import type { RecentItem, UnrestrictedLinkEntity } from "metabase-types/api";
+import { useRecentItemListQuery } from "metabase/common/hooks";
+import type { IconName } from "metabase/core/components/Icon";
+import RecentItems from "metabase/entities/recent-items";
+import { useDispatch } from "metabase/lib/redux";
+import { RecentsListContent } from "metabase/nav/components/search/RecentsList/RecentsListContent";
+import {
+  getItemName,
+  getItemUrl,
+} from "metabase/nav/components/search/RecentsList/util";
+import { Paper } from "metabase/ui";
+
+type RecentsListProps = {
+  onClick?: (elem: UnrestrictedLinkEntity) => void;
+  className?: string;
+};
+
+export interface WrappedRecentItem extends RecentItem {
+  getUrl: () => string;
+  getIcon: () => {
+    name: IconName;
+    size?: number;
+    width?: number;
+    height?: number;
+  };
+}
+
+export const RecentsList = ({ onClick, className }: RecentsListProps) => {
+  const { data = [], isLoading: isRecentsListLoading } =
+    useRecentItemListQuery();
+
+  const wrappedResults: WrappedRecentItem[] = useMemo(
+    () => data.map(item => RecentItems.wrapEntity(item)),
+    [data],
+  );
+
+  const dispatch = useDispatch();
+
+  const onChangeLocation = (item: RecentItem) => {
+    const url = getItemUrl(item);
+    if (url) {
+      dispatch(push(url));
+    }
+  };
+
+  const onContainerClick = (item: RecentItem) => {
+    if (onClick) {
+      onClick({
+        ...item.model_object,
+        model: item.model,
+        name: getItemName(item),
+        id: item.model_id,
+      });
+    } else {
+      onChangeLocation(item);
+    }
+  };
+
+  return (
+    <Paper withBorder className={className}>
+      <RecentsListContent
+        isLoading={isRecentsListLoading}
+        results={wrappedResults}
+        onClick={onContainerClick}
+      />
+    </Paper>
+  );
+};
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.unit.spec.js b/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.unit.spec.js
index d145bef295d546e87922ef3f1dbdf1c2c55576af..26de1ce03c56d04b3ec6e13c58bc4efa47fab592 100644
--- a/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.unit.spec.js
+++ b/frontend/src/metabase/nav/components/search/RecentsList/RecentsList.unit.spec.js
@@ -1,6 +1,6 @@
 import fetchMock from "fetch-mock";
 import { renderWithProviders, screen } from "__support__/ui";
-import RecentsList from "./RecentsList";
+import { RecentsList } from "./RecentsList";
 
 const recentsData = [
   {
@@ -59,7 +59,7 @@ describe("RecentsList", () => {
     expect(screen.getByText("Recently viewed")).toBeInTheDocument();
 
     const [questionType, dashboardType, tableType] = screen.queryAllByTestId(
-      "recently-viewed-item-type",
+      "result-link-wrapper",
     );
 
     expect(screen.getByText("Question I visited")).toBeInTheDocument();
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/RecentsListContent.tsx b/frontend/src/metabase/nav/components/search/RecentsList/RecentsListContent.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e92647886d4be137aa26fa470bdad84139601f9c
--- /dev/null
+++ b/frontend/src/metabase/nav/components/search/RecentsList/RecentsListContent.tsx
@@ -0,0 +1,123 @@
+import { t } from "ttag";
+import type { RecentItem } from "metabase-types/api";
+import { getTranslatedEntityName } from "metabase/common/utils/model-names";
+import EmptyState from "metabase/components/EmptyState";
+import { useListKeyboardNavigation } from "metabase/hooks/use-list-keyboard-navigation";
+import { isSyncCompleted } from "metabase/lib/syncing";
+import type { WrappedRecentItem } from "metabase/nav/components/search/RecentsList";
+import {
+  SearchLoadingSpinner,
+  EmptyStateContainer,
+} from "metabase/nav/components/search/SearchResults";
+
+import {
+  ItemIcon,
+  LoadingSection,
+  ModerationIcon,
+  ResultNameSection,
+  ResultTitle,
+  SearchResultContainer,
+} from "metabase/search/components/SearchResult";
+import { SearchResultLink } from "metabase/search/components/SearchResultLink";
+import { Group, Loader, Stack, Title } from "metabase/ui";
+import { getItemName, getItemUrl, isItemActive } from "./util";
+
+type RecentsListContentProps = {
+  isLoading: boolean;
+  results: WrappedRecentItem[];
+  onClick?: (item: RecentItem) => void;
+};
+
+export const RecentsListContent = ({
+  isLoading,
+  results,
+  onClick,
+}: RecentsListContentProps) => {
+  const { getRef, cursorIndex } = useListKeyboardNavigation<
+    WrappedRecentItem,
+    HTMLButtonElement
+  >({
+    list: results,
+    onEnter: (item: WrappedRecentItem) => onClick?.(item),
+  });
+
+  if (isLoading) {
+    return <SearchLoadingSpinner />;
+  }
+
+  if (results.length === 0) {
+    return (
+      <Stack spacing="md" px="sm" py="md">
+        <Title order={4} px="sm">{t`Recently viewed`}</Title>
+        <EmptyStateContainer>
+          <EmptyState message={t`Nothing here`} icon="folder" />
+        </EmptyStateContainer>
+      </Stack>
+    );
+  }
+
+  return (
+    <Stack spacing="md" px="sm" py="md" data-testid="recents-list-container">
+      <Title order={4} px="sm">{t`Recently viewed`}</Title>
+      <Stack spacing={0}>
+        {results.map((item, index) => {
+          const isActive = isItemActive(item);
+
+          return (
+            <SearchResultContainer
+              data-testid="recently-viewed-item"
+              ref={getRef(item)}
+              key={getItemKey(item)}
+              component="button"
+              onClick={() => onClick?.(item)}
+              isActive={isActive}
+              isSelected={cursorIndex === index}
+              p="sm"
+            >
+              <ItemIcon active={isActive} item={item} type={item.model} />
+              <ResultNameSection justify="center" spacing="xs">
+                <Group spacing="xs" align="center" noWrap>
+                  <ResultTitle
+                    data-testid="recently-viewed-item-title"
+                    truncate
+                    href={getItemUrl(item) ?? undefined}
+                  >
+                    {getItemName(item)}
+                  </ResultTitle>
+                  <ModerationIcon
+                    status={getModeratedStatus(item)}
+                    filled
+                    size={14}
+                  />
+                </Group>
+                <SearchResultLink>
+                  {getTranslatedEntityName(item.model)}
+                </SearchResultLink>
+              </ResultNameSection>
+              {isItemLoading(item) && (
+                <LoadingSection px="xs">
+                  <Loader />
+                </LoadingSection>
+              )}
+            </SearchResultContainer>
+          );
+        })}
+      </Stack>
+    </Stack>
+  );
+};
+
+const getItemKey = ({ model, model_id }: RecentItem) => {
+  return `${model}:${model_id}`;
+};
+
+const getModeratedStatus = ({ model_object }: RecentItem) => {
+  return model_object.moderated_status;
+};
+
+const isItemLoading = ({ model, model_object }: RecentItem) => {
+  if (model !== "table") {
+    return false;
+  }
+  return !isSyncCompleted(model_object);
+};
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/index.ts b/frontend/src/metabase/nav/components/search/RecentsList/index.ts
index a88f680378c4ab588abcf93870c5faf05ffca5be..4a871263725c8560217303b3d1e1e4cf8aceb79f 100644
--- a/frontend/src/metabase/nav/components/search/RecentsList/index.ts
+++ b/frontend/src/metabase/nav/components/search/RecentsList/index.ts
@@ -1 +1,2 @@
-export { default as RecentsList } from "./RecentsList";
+export { RecentsList } from "./RecentsList";
+export type { WrappedRecentItem } from "./RecentsList";
diff --git a/frontend/src/metabase/nav/components/search/RecentsList/util.ts b/frontend/src/metabase/nav/components/search/RecentsList/util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..65c28bc4be7becd2960150a1e8551a7c27bd8cb7
--- /dev/null
+++ b/frontend/src/metabase/nav/components/search/RecentsList/util.ts
@@ -0,0 +1,18 @@
+import type { RecentItem } from "metabase-types/api";
+
+import { isSyncCompleted } from "metabase/lib/syncing";
+import * as Urls from "metabase/lib/urls";
+
+export const getItemName = ({ model_object }: RecentItem) => {
+  return model_object.display_name || model_object.name;
+};
+
+export const isItemActive = ({ model, model_object }: RecentItem) => {
+  if (model !== "table") {
+    return true;
+  }
+  return isSyncCompleted(model_object);
+};
+
+export const getItemUrl = (item: RecentItem) =>
+  isItemActive(item) ? Urls.modelToUrl(item) : "";
diff --git a/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.styled.tsx b/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.styled.tsx
index 95d19b3f9bbc40ef6ddc14068da6c572d4417843..b4d9dd43c209252e61ef713451c62e3ae53d0a23 100644
--- a/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.styled.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.styled.tsx
@@ -25,24 +25,35 @@ export const SearchBarRoot = styled.div`
   }
 `;
 
-export const SearchInputContainer = styled.div<{ isActive: boolean }>`
+export const SearchInputContainer = styled.div<{
+  isActive: boolean;
+}>`
   display: flex;
   flex: 1 1 auto;
   align-items: center;
   position: relative;
 
-  background-color: ${props =>
-    props.isActive ? color("bg-medium") : color("bg-light")};
+  ${({ isActive }) => {
+    if (isActive) {
+      return css`
+        background-color: ${color("bg-medium")};
+      `;
+    }
+    return css`
+      background-color: ${color("white")};
+
+      &:hover {
+        background-color: ${color("bg-light")};
+      }
+    `;
+  }}
+
   border: 1px solid ${color("border")};
 
   overflow: hidden;
 
   transition: background 150ms, width 0.2s;
 
-  &:hover {
-    background-color: ${color("bg-medium")};
-  }
-
   @media (prefers-reduced-motion) {
     transition: none;
   }
@@ -69,7 +80,9 @@ export const SearchInputContainer = styled.div<{ isActive: boolean }>`
   }
 `;
 
-export const SearchInput = styled.input<{ isActive: boolean }>`
+export const SearchInput = styled.input<{
+  isActive: boolean;
+}>`
   background-color: transparent;
   border: none;
   color: ${({ theme }) => theme.colors.text[2]};
@@ -106,7 +119,9 @@ export const SearchInput = styled.input<{ isActive: boolean }>`
 
 const ICON_MARGIN = "10px";
 
-export const SearchIcon = styled(Icon)<{ isActive: boolean }>`
+export const SearchIcon = styled(Icon)<{
+  isActive: boolean;
+}>`
   ${breakpointMaxSmall} {
     transition: margin 0.3s;
 
diff --git a/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.tsx b/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.tsx
index 569c7420ca4dd453ddd37a1d280b2fd166194acb..71c6fceb23ff470d41ffa5e63c181fee35aeaab0 100644
--- a/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.tsx
@@ -1,5 +1,5 @@
 import type { MouseEvent } from "react";
-import { useEffect, useCallback, useRef, useState } from "react";
+import { useEffect, useCallback, useRef, useState, useMemo } from "react";
 import { t } from "ttag";
 import { push } from "react-router-redux";
 import { withRouter } from "react-router";
@@ -19,7 +19,11 @@ import { getSetting } from "metabase/selectors/settings";
 import { RecentsList } from "metabase/nav/components/search/RecentsList";
 
 import type { SearchAwareLocation, WrappedResult } from "metabase/search/types";
-import { getSearchTextFromLocation } from "metabase/search/utils";
+import {
+  getFiltersFromLocation,
+  getSearchTextFromLocation,
+  isSearchPageLocation,
+} from "metabase/search/utils";
 import { SearchResultsDropdown } from "metabase/nav/components/search/SearchResultsDropdown";
 import {
   SearchInputContainer,
@@ -52,6 +56,11 @@ function SearchBarView({ location, onSearchActive, onSearchInactive }: Props) {
     getSearchTextFromLocation(location),
   );
 
+  const searchFilters = useMemo(
+    () => getFiltersFromLocation(location),
+    [location],
+  );
+
   const [isActive, { turnOn: setActive, turnOff: setInactive }] =
     useToggle(false);
 
@@ -136,11 +145,18 @@ function SearchBarView({ location, onSearchActive, onSearchInactive }: Props) {
   }, [previousLocation, location, setInactive]);
 
   const goToSearchApp = useCallback(() => {
+    const shouldPersistFilters = isSearchPageLocation(previousLocation);
+    const filters = shouldPersistFilters ? searchFilters : {};
+
+    const query = {
+      q: searchText.trim(),
+      ...filters,
+    };
     onChangeLocation({
       pathname: "search",
-      query: { q: searchText.trim() },
+      query,
     });
-  }, [onChangeLocation, searchText]);
+  }, [onChangeLocation, previousLocation, searchFilters, searchText]);
 
   const handleInputKeyPress = useCallback(
     e => {
diff --git a/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.unit.spec.tsx b/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.unit.spec.tsx
index 95ccd47cda11868e943d608340b281d712b253a1..43c4a8012b4d38f52cdd8ce4ab37abd53eeede9d 100644
--- a/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.unit.spec.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchBar/SearchBar.unit.spec.tsx
@@ -5,15 +5,19 @@ import {
   screen,
   within,
   waitForLoaderToBeRemoved,
+  waitFor,
 } from "__support__/ui";
 import {
+  setupCollectionsEndpoints,
   setupRecentViewsEndpoints,
   setupSearchEndpoints,
+  setupUsersEndpoints,
 } from "__support__/server-mocks";
 import {
   createMockCollectionItem,
   createMockModelObject,
   createMockRecentItem,
+  createMockUser,
 } from "metabase-types/api/mocks";
 import {
   createMockSettingsState,
@@ -61,6 +65,8 @@ const setup = ({
 
   setupSearchEndpoints(searchResultItems);
   setupRecentViewsEndpoints(recentViewsItems);
+  setupUsersEndpoints([createMockUser()]);
+  setupCollectionsEndpoints({ collections: [] });
 
   const { history } = renderWithProviders(
     <Route path="*" component={SearchBar} />,
@@ -120,9 +126,19 @@ describe("SearchBar", () => {
       userEvent.click(getSearchBar());
       userEvent.type(getSearchBar(), "BC");
 
+      // wait for dropdown to open
+      await waitForLoaderToBeRemoved();
+
       const resultItems = await screen.findAllByTestId("search-result-item");
       expect(resultItems.length).toBe(2);
 
+      // wait for all of the elements of the search result to load
+      await waitFor(() => {
+        expect(
+          screen.queryByTestId("info-text-collection-loading-text"),
+        ).not.toBeInTheDocument();
+      });
+
       // There are two search results, each with a link to `Our analytics`,
       // so we want to navigate to the search result, then the collection link.
       for (const cardName of ["Card ABC", "Card BCD"]) {
@@ -133,7 +149,7 @@ describe("SearchBar", () => {
         );
 
         expect(filteredElement).not.toBeUndefined();
-        expect(filteredElement).toHaveFocus();
+        expect(screen.getByText(cardName)).toHaveFocus();
 
         userEvent.tab();
 
@@ -161,4 +177,34 @@ describe("SearchBar", () => {
       expect(getSearchBar()).toHaveValue("");
     });
   });
+
+  describe("persisting search filters", () => {
+    it("should keep URL search filters when changing the text query", () => {
+      const { history } = setup({
+        initialRoute: "/search?q=foo&type=card",
+      });
+
+      userEvent.clear(getSearchBar());
+      userEvent.type(getSearchBar(), "bar{enter}");
+
+      const location = history.getCurrentLocation();
+
+      expect(location.pathname).toEqual("search");
+      expect(location.search).toEqual("?q=bar&type=card");
+    });
+
+    it("should not keep URL search filters when not in the search app", () => {
+      const { history } = setup({
+        initialRoute: "/collection/root?q=foo&type=card&type=dashboard",
+      });
+
+      userEvent.clear(getSearchBar());
+      userEvent.type(getSearchBar(), "bar{enter}");
+
+      const location = history.getCurrentLocation();
+
+      expect(location.pathname).toEqual("search");
+      expect(location.search).toEqual("?q=bar");
+    });
+  });
 });
diff --git a/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.styled.tsx b/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.styled.tsx
index 22ce7fea167bfbe3f2dd1f3e8222b68eacf53eec..ef8105274552444438ab8735721ba098fe31ba5a 100644
--- a/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.styled.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.styled.tsx
@@ -1,11 +1,20 @@
 import styled from "@emotion/styled";
+import { Stack } from "metabase/ui";
 
 export const EmptyStateContainer = styled.div`
   margin-top: 4rem;
   margin-bottom: 2rem;
 `;
 
-export const SearchResultsList = styled.ul`
+export const SearchResultsList = styled(Stack)`
+  overflow: hidden;
+`;
+
+export const ResultsContainer = styled.ul`
   overflow-y: auto;
-  padding: 0.5rem 0;
+  padding: 0.5rem;
+`;
+
+export const ResultsFooter = styled.li`
+  list-style-type: none;
 `;
diff --git a/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.tsx b/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.tsx
index 0dad26fcaaedba52102d1185919924ccca3b03a8..08ef8046b21fba1b9210d1a33d9d10b017157d4c 100644
--- a/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
 import { t } from "ttag";
 import { push } from "react-router-redux";
 import { useDebounce } from "react-use";
@@ -21,20 +21,40 @@ import type {
 } from "metabase-types/api";
 import {
   EmptyStateContainer,
+  ResultsContainer,
+  ResultsFooter,
   SearchResultsList,
 } from "metabase/nav/components/search/SearchResults/SearchResults.styled";
 
+export type SearchResultsFooter =
+  | (({
+      metadata,
+      isSelected,
+    }: {
+      metadata: Omit<SearchResultsType, "data">;
+      isSelected?: boolean;
+    }) => JSX.Element | null)
+  | null;
+
 export type SearchResultsProps = {
   onEntitySelect?: (result: any) => void;
   forceEntitySelect?: boolean;
   searchText?: string;
   searchFilters?: SearchFilters;
   models?: SearchModelType[];
-  footerComponent?:
-    | ((metadata: Omit<SearchResultsType, "data">) => JSX.Element | null)
-    | null;
+  footerComponent?: SearchResultsFooter;
+  onFooterSelect?: () => void;
 };
 
+export const SearchLoadingSpinner = () => (
+  <Stack p="xl" align="center">
+    <Loader size="lg" data-testid="loading-spinner" />
+    <Text size="xl" color="text.0">
+      {t`Loading…`}
+    </Text>
+  </Stack>
+);
+
 export const SearchResults = ({
   onEntitySelect,
   forceEntitySelect = false,
@@ -42,10 +62,12 @@ export const SearchResults = ({
   searchFilters = {},
   models,
   footerComponent,
+  onFooterSelect,
 }: SearchResultsProps) => {
   const dispatch = useDispatch();
 
   const [debouncedSearchText, setDebouncedSearchText] = useState<string>();
+
   const isWaitingForDebounce = searchText !== debouncedSearchText;
 
   useDebounce(
@@ -73,14 +95,33 @@ export const SearchResults = ({
     enabled: !!debouncedSearchText,
   });
 
+  const hasResults = list.length > 0;
+  const showFooter = hasResults && footerComponent && metadata;
+
+  const dropdownItemList = useMemo(() => {
+    return showFooter ? [...list, footerComponent] : list;
+  }, [footerComponent, list, showFooter]);
+
+  const onEnterSelect = (item?: CollectionItem | SearchResultsFooter) => {
+    if (showFooter && cursorIndex === dropdownItemList.length - 1) {
+      onFooterSelect?.();
+    }
+
+    if (item && typeof item !== "function") {
+      if (onEntitySelect) {
+        onEntitySelect(Search.wrapEntity(item, dispatch));
+      } else if (item && item.getUrl) {
+        dispatch(push(item.getUrl()));
+      }
+    }
+  };
+
   const { reset, getRef, cursorIndex } = useListKeyboardNavigation<
-    CollectionItem,
+    CollectionItem | SearchResultsProps["footerComponent"],
     HTMLLIElement
   >({
-    list,
-    onEnter: onEntitySelect
-      ? item => onEntitySelect(Search.wrapEntity(item, dispatch))
-      : item => dispatch(push(item.getUrl())),
+    list: dropdownItemList,
+    onEnter: onEnterSelect,
     resetOnListChange: false,
   });
 
@@ -88,51 +129,47 @@ export const SearchResults = ({
     reset();
   }, [searchText, reset]);
 
-  const hasResults = list.length > 0;
-  const showFooter = hasResults && footerComponent && metadata;
-
   if (isLoading || isWaitingForDebounce) {
-    return (
-      <Stack p="xl" align="center">
-        <Loader size="lg" data-testid="loading-spinner" />
-        <Text size="xl" color="text.0">
-          {t`Loading…`}
-        </Text>
-      </Stack>
-    );
+    return <SearchLoadingSpinner />;
   }
 
-  return (
-    <>
-      <SearchResultsList data-testid="search-results-list">
-        {hasResults ? (
-          list.map((item, index) => {
-            const isIndexedEntity = item.model === "indexed-entity";
-            const onClick =
-              onEntitySelect && (isIndexedEntity || forceEntitySelect)
-                ? onEntitySelect
-                : undefined;
-            const ref = getRef(item);
-            const wrappedResult = Search.wrapEntity(item, dispatch);
-
-            return (
-              <li key={`${item.model}:${item.id}`} ref={ref}>
-                <SearchResult
-                  result={wrappedResult}
-                  compact={true}
-                  isSelected={cursorIndex === index}
-                  onClick={onClick}
-                />
-              </li>
-            );
-          })
-        ) : (
-          <EmptyStateContainer>
-            <EmptyState message={t`Didn't find anything`} icon="search" />
-          </EmptyStateContainer>
-        )}
-      </SearchResultsList>
-      {showFooter && footerComponent(metadata)}
-    </>
+  return hasResults ? (
+    <SearchResultsList data-testid="search-results-list" spacing={0}>
+      <ResultsContainer>
+        {list.map((item, index) => {
+          const isIndexedEntity = item.model === "indexed-entity";
+          const onClick =
+            onEntitySelect && (isIndexedEntity || forceEntitySelect)
+              ? onEntitySelect
+              : undefined;
+          const ref = getRef(item);
+          const wrappedResult = Search.wrapEntity(item, dispatch);
+
+          return (
+            <li key={`${item.model}:${item.id}`} ref={ref}>
+              <SearchResult
+                result={wrappedResult}
+                compact={true}
+                showDescription={true}
+                isSelected={cursorIndex === index}
+                onClick={onClick}
+              />
+            </li>
+          );
+        })}
+      </ResultsContainer>
+      {showFooter && (
+        <ResultsFooter ref={getRef(footerComponent)}>
+          {footerComponent({
+            metadata,
+            isSelected: cursorIndex === dropdownItemList.length - 1,
+          })}
+        </ResultsFooter>
+      )}
+    </SearchResultsList>
+  ) : (
+    <EmptyStateContainer data-testid="search-results-empty-state">
+      <EmptyState message={t`Didn't find anything`} icon="search" />
+    </EmptyStateContainer>
   );
 };
diff --git a/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.unit.spec.tsx b/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.unit.spec.tsx
index daa5a25f7241347ae887f56d83e6af9ab40511a3..1c51d30049c9353d4aa5960190e2eed723c3a13f 100644
--- a/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.unit.spec.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchResults/SearchResults.unit.spec.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/prop-types */
 import userEvent from "@testing-library/user-event";
 import { Route } from "react-router";
 import {
@@ -5,23 +6,29 @@ import {
   screen,
   waitForLoaderToBeRemoved,
 } from "__support__/ui";
-import { setupSearchEndpoints } from "__support__/server-mocks";
-import type {
-  SearchResult,
-  SearchResults as SearchResultsType,
-} from "metabase-types/api";
-import { createMockSearchResult } from "metabase-types/api/mocks";
+import {
+  setupCollectionByIdEndpoint,
+  setupSearchEndpoints,
+  setupUsersEndpoints,
+} from "__support__/server-mocks";
+import type { SearchResult } from "metabase-types/api";
+import {
+  createMockCollection,
+  createMockSearchResult,
+  createMockUser,
+} from "metabase-types/api/mocks";
 import { checkNotNull } from "metabase/core/utils/types";
+import type { SearchResultsFooter } from "metabase/nav/components/search/SearchResults";
 import { SearchResults } from "metabase/nav/components/search/SearchResults";
 
 type SearchResultsSetupProps = {
   searchResults?: SearchResult[];
   forceEntitySelect?: boolean;
   searchText?: string;
-  footer?: ((metadata: Omit<SearchResultsType, "data">) => JSX.Element) | null;
+  footer?: SearchResultsFooter;
 };
 
-const TEST_FOOTER = (metadata: Omit<SearchResultsType, "data">) => (
+const TEST_FOOTER: SearchResultsFooter = ({ metadata }) => (
   <div data-testid="footer">
     <div data-testid="test-total">{metadata.total}</div>
   </div>
@@ -41,6 +48,10 @@ const setup = async ({
   footer = null,
 }: SearchResultsSetupProps = {}) => {
   setupSearchEndpoints(searchResults);
+  setupUsersEndpoints([createMockUser()]);
+  setupCollectionByIdEndpoint({
+    collections: [createMockCollection()],
+  });
 
   const onEntitySelect = jest.fn();
 
diff --git a/frontend/src/metabase/nav/components/search/SearchResults/index.ts b/frontend/src/metabase/nav/components/search/SearchResults/index.ts
index 98ef71eaff9624e7d286d356892b5f697b023218..79c3e1de483d676401502446a9da57a931b96874 100644
--- a/frontend/src/metabase/nav/components/search/SearchResults/index.ts
+++ b/frontend/src/metabase/nav/components/search/SearchResults/index.ts
@@ -1 +1,3 @@
-export { SearchResults } from "./SearchResults";
+export { SearchResults, SearchLoadingSpinner } from "./SearchResults";
+export type { SearchResultsFooter } from "./SearchResults";
+export * from "./SearchResults.styled";
diff --git a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.styled.tsx b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.styled.tsx
index 295d9730a1e66212a8545bf2b0a66801c6bfdd63..c0f522d76d6633bb64478b6c29ad349ae941d4d0 100644
--- a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.styled.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.styled.tsx
@@ -1,10 +1,12 @@
+import type { Theme } from "@emotion/react";
+import { css } from "@emotion/react";
 import styled from "@emotion/styled";
 import {
   breakpointMaxSmall,
   breakpointMinSmall,
 } from "metabase/styled-components/theme";
 import { APP_BAR_HEIGHT } from "metabase/nav/constants";
-import type { PaperProps } from "metabase/ui";
+import type { PaperProps, GroupProps } from "metabase/ui";
 import { Group, Paper } from "metabase/ui";
 
 export const SearchResultsContainer = styled(Paper)<PaperProps>`
@@ -20,12 +22,20 @@ export const SearchResultsContainer = styled(Paper)<PaperProps>`
   }
 `;
 
-export const SearchDropdownFooter = styled(Group)`
+const selectedStyles = ({ theme }: { theme: Theme }) => css`
+  color: ${theme.colors.brand[1]};
+  background-color: ${theme.colors.brand[0]};
+  cursor: pointer;
+  transition: all 0.2s ease-in-out;
+`;
+
+export const SearchDropdownFooter = styled(Group, {
+  shouldForwardProp: propName => propName !== "isSelected",
+})<{ isSelected?: boolean } & GroupProps>`
   border-top: 1px solid ${({ theme }) => theme.colors.border[0]};
 
+  ${({ theme, isSelected }) => isSelected && selectedStyles({ theme })}
   &:hover {
-    color: ${({ theme }) => theme.colors.brand[1]};
-    cursor: pointer;
-    transition: color 0.2s ease-in-out;
+    ${({ theme }) => selectedStyles({ theme })}
   }
 `;
diff --git a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.tsx b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.tsx
index 046f42b351b50f80e003b9d034cfe9a1b1986e36..8a0702eaee02792bb366f23f25d0e0f63b6826d8 100644
--- a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.tsx
@@ -1,13 +1,14 @@
-import { jt } from "ttag";
+import { jt, t } from "ttag";
 import { SearchResults } from "metabase/nav/components/search/SearchResults";
 import type { WrappedResult } from "metabase/search/types";
 import { Text } from "metabase/ui";
 import { Icon } from "metabase/core/components/Icon";
-import type { SearchResultsProps } from "metabase/nav/components/search/SearchResults/SearchResults";
+import type { SearchResultsFooter } from "metabase/nav/components/search/SearchResults";
 import {
   SearchDropdownFooter,
   SearchResultsContainer,
 } from "./SearchResultsDropdown.styled";
+import { MIN_RESULTS_FOR_FOOTER_TEXT } from "./constants";
 
 export type SearchResultsDropdownProps = {
   searchText: string;
@@ -20,8 +21,13 @@ export const SearchResultsDropdown = ({
   onSearchItemSelect,
   goToSearchApp,
 }: SearchResultsDropdownProps) => {
-  const renderFooter: SearchResultsProps["footerComponent"] = metadata =>
-    metadata.total > 1 ? (
+  const renderFooter: SearchResultsFooter = ({ metadata, isSelected }) => {
+    const resultText =
+      metadata.total > MIN_RESULTS_FOR_FOOTER_TEXT
+        ? jt`View and filter all ${metadata.total} results`
+        : t`View and filter results`;
+
+    return metadata.total > 0 ? (
       <SearchDropdownFooter
         data-testid="search-dropdown-footer"
         position="apart"
@@ -29,15 +35,15 @@ export const SearchResultsDropdown = ({
         px="lg"
         py="0.625rem"
         onClick={goToSearchApp}
+        isSelected={isSelected}
       >
-        <Text
-          weight={700}
-          size="sm"
-          c="inherit"
-        >{jt`View and filter all ${metadata.total} results`}</Text>
+        <Text weight={700} size="sm" c="inherit">
+          {resultText}
+        </Text>
         <Icon name="arrow_right" size={14} />
       </SearchDropdownFooter>
     ) : null;
+  };
 
   return (
     <SearchResultsContainer
@@ -48,6 +54,7 @@ export const SearchResultsDropdown = ({
         searchText={searchText.trim()}
         onEntitySelect={onSearchItemSelect}
         footerComponent={renderFooter}
+        onFooterSelect={goToSearchApp}
       />
     </SearchResultsContainer>
   );
diff --git a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.unit.spec.tsx b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.unit.spec.tsx
index a1392d517e0fa65fb2271de5ba4c6df2d60fad15..43c3886123b438bc4295f9b373970dd6004fbd02 100644
--- a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.unit.spec.tsx
+++ b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/SearchResultsDropdown.unit.spec.tsx
@@ -5,15 +5,41 @@ import {
   screen,
   waitForLoaderToBeRemoved,
 } from "__support__/ui";
-import { createMockSearchResult } from "metabase-types/api/mocks";
-import { setupSearchEndpoints } from "__support__/server-mocks";
+import {
+  createMockCollection,
+  createMockSearchResult,
+  createMockUser,
+} from "metabase-types/api/mocks";
+import {
+  setupCollectionByIdEndpoint,
+  setupSearchEndpoints,
+  setupUsersEndpoints,
+} from "__support__/server-mocks";
 import type { SearchResult } from "metabase-types/api";
 import { checkNotNull } from "metabase/core/utils/types";
 import { SearchResultsDropdown } from "./SearchResultsDropdown";
 
+// Mock MIN_RESULTS_FOR_FOOTER_TEXT so we don't have to generate a ton of elements for the footer test
+jest.mock(
+  "metabase/nav/components/search/SearchResultsDropdown/constants",
+  () => ({
+    MIN_RESULTS_FOR_FOOTER_TEXT: 1,
+  }),
+);
+
+const TEST_COLLECTION = createMockCollection();
+
 const TEST_SEARCH_RESULTS = [
-  createMockSearchResult({ id: 1, name: "Test 1" }),
-  createMockSearchResult({ id: 2, name: "Test 2" }),
+  createMockSearchResult({
+    id: 1,
+    name: "Test 1",
+    collection: TEST_COLLECTION,
+  }),
+  createMockSearchResult({
+    id: 2,
+    name: "Test 2",
+    collection: TEST_COLLECTION,
+  }),
   createMockSearchResult({
     id: 3,
     name: "Indexed Entity",
@@ -29,6 +55,10 @@ const setup = async ({
   const goToSearchApp = jest.fn();
 
   setupSearchEndpoints(searchResults);
+  setupUsersEndpoints([createMockUser()]);
+  setupCollectionByIdEndpoint({
+    collections: [TEST_COLLECTION],
+  });
 
   const { history } = renderWithProviders(
     <Route
@@ -58,11 +88,9 @@ describe("SearchResultsDropdown", () => {
 
     expect(searchItem).toHaveTextContent("Test 1");
 
-    const href = checkNotNull(searchItem.getAttribute("href"));
-
     userEvent.click(searchItem);
 
-    expect(history.getCurrentLocation().pathname).toEqual(href);
+    expect(history.getCurrentLocation().pathname).toEqual("/question/1-test-1");
   });
 
   it("should call goToSearchApp when the footer is clicked", async () => {
@@ -88,8 +116,21 @@ describe("SearchResultsDropdown", () => {
     ).not.toBeInTheDocument();
   });
 
-  it("should render the footer if there are search results", async () => {
-    await setup();
+  it("should only render 'View all results' if there are less than MAX_SEARCH_RESULTS_FOR_FOOTER results for the footer", async () => {
+    await setup({ searchResults: TEST_SEARCH_RESULTS.slice(0, 1) });
+    expect(screen.getByTestId("search-dropdown-footer")).toBeInTheDocument();
+    expect(screen.getByText("View and filter results")).toBeInTheDocument();
+  });
+
+  it("should render 'View all X results' if there are more than MAX_SEARCH_RESULTS_FOR_FOOTER results for the footer", async () => {
+    await setup({
+      searchText: "e",
+    });
     expect(screen.getByTestId("search-dropdown-footer")).toBeInTheDocument();
+    expect(
+      screen.getByText(
+        `View and filter all ${TEST_SEARCH_RESULTS.length} results`,
+      ),
+    ).toBeInTheDocument();
   });
 });
diff --git a/frontend/src/metabase/nav/components/search/SearchResultsDropdown/constants.ts b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/constants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e8960e8eadec11f18867cc9c23baf139326cf159
--- /dev/null
+++ b/frontend/src/metabase/nav/components/search/SearchResultsDropdown/constants.ts
@@ -0,0 +1 @@
+export const MIN_RESULTS_FOR_FOOTER_TEXT = 4;
diff --git a/frontend/src/metabase/parameters/utils/date-formatting.ts b/frontend/src/metabase/parameters/utils/date-formatting.ts
index e74acd1ec91bb08e8626e6d6b46386c5f9a3700e..05d64e483a1cb955b310067966b558bf47fbe012 100644
--- a/frontend/src/metabase/parameters/utils/date-formatting.ts
+++ b/frontend/src/metabase/parameters/utils/date-formatting.ts
@@ -100,7 +100,7 @@ const prefixedOperators = new Set([
   "not-empty",
 ]);
 
-function getFilterTitle(filter: any[]) {
+export function getFilterTitle(filter: any[]) {
   const values = generateTimeFilterValuesDescriptions(filter);
   const desc =
     values.length > 2
diff --git a/frontend/src/metabase/plugins/index.ts b/frontend/src/metabase/plugins/index.ts
index cef4b790072dac8ca25afc956fec96b558cd5177..eb232f93b91d809966c495b6c0d69f962cd4c626 100644
--- a/frontend/src/metabase/plugins/index.ts
+++ b/frontend/src/metabase/plugins/index.ts
@@ -24,6 +24,7 @@ import type {
 } from "metabase-types/api";
 import type { AdminPathKey, State } from "metabase-types/store";
 import type { ADMIN_SETTINGS_SECTIONS } from "metabase/admin/settings/selectors";
+import type { SearchFilterComponent } from "metabase/search/types";
 import type Question from "metabase-lib/Question";
 
 import type Database from "metabase-lib/metadata/Database";
@@ -159,8 +160,11 @@ export const PLUGIN_COLLECTIONS = {
   ): AuthorityLevelMenuItem[] => [],
 };
 
-type CollectionAuthorityLevelIcon = ComponentType<
-  Omit<IconProps, "name" | "tooltip"> & { collection: Collection }
+export type CollectionAuthorityLevelIcon = ComponentType<
+  Omit<IconProps, "name" | "tooltip"> & {
+    collection: Pick<Collection, "authority_level">;
+    tooltip?: "default" | "belonging";
+  }
 >;
 
 type FormCollectionAuthorityLevelPicker = ComponentType<
@@ -286,3 +290,7 @@ export const PLUGIN_MODEL_PERSISTENCE = {
 export const PLUGIN_EMBEDDING = {
   isEnabled: () => false,
 };
+
+export const PLUGIN_CONTENT_VERIFICATION = {
+  VerifiedFilter: {} as SearchFilterComponent<"verified">,
+};
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/data-search/SearchResults.jsx b/frontend/src/metabase/query_builder/components/DataSelector/data-search/SearchResults.jsx
index bcf837801d1d3de6985120a30930cbdff4005c72..afab2e4064c643d31c365ff6caa0cb29ff77326c 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/data-search/SearchResults.jsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/data-search/SearchResults.jsx
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
 import { t } from "ttag";
 
 import { Icon } from "metabase/core/components/Icon";
-import { SearchResult } from "metabase/search/components/SearchResult";
+import { SearchResult } from "metabase/search/components/SearchResult/SearchResult";
 import { DEFAULT_SEARCH_LIMIT } from "metabase/lib/constants";
 import Search from "metabase/entities/search";
 
@@ -55,7 +55,7 @@ export function SearchResults({
                     result={item}
                     onClick={onSelect}
                     compact
-                    hasDescription={false}
+                    showDescription={false}
                   />
                 </li>
               ))}
diff --git a/frontend/src/metabase/search/components/CollectionBadge.styled.tsx b/frontend/src/metabase/search/components/CollectionBadge.styled.tsx
index 59044907a05b1839d2e2245d5e036fcf2004f15b..9892f907d661ad1bf5b789e280b8453793215650 100644
--- a/frontend/src/metabase/search/components/CollectionBadge.styled.tsx
+++ b/frontend/src/metabase/search/components/CollectionBadge.styled.tsx
@@ -2,10 +2,6 @@ import styled from "@emotion/styled";
 import { color } from "metabase/lib/colors";
 import Link from "metabase/core/components/Link";
 
-import { PLUGIN_COLLECTION_COMPONENTS } from "metabase/plugins";
-
-const { CollectionAuthorityLevelIcon } = PLUGIN_COLLECTION_COMPONENTS;
-
 export const CollectionBadgeRoot = styled.div`
   display: inline-block;
 `;
@@ -14,15 +10,8 @@ export const CollectionLink = styled(Link)`
   display: flex;
   align-items: center;
   text-decoration: dashed;
+
   &:hover {
     color: ${color("brand")};
   }
 `;
-
-export const AuthorityLevelIcon = styled(CollectionAuthorityLevelIcon)`
-  padding-right: 2px;
-`;
-
-AuthorityLevelIcon.defaultProps = {
-  size: 13,
-};
diff --git a/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c1b8b67db255182e91660d17005258696ccd1bb0
--- /dev/null
+++ b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.styled.tsx
@@ -0,0 +1,54 @@
+import styled from "@emotion/styled";
+import { Group } from "metabase/ui";
+
+import FieldSet from "metabase/components/FieldSet";
+import EventSandbox from "metabase/components/EventSandbox";
+import { Icon } from "metabase/core/components/Icon";
+
+export const DropdownFieldSet = styled(FieldSet)<{
+  fieldHasValueOrFocus?: boolean;
+}>`
+  min-width: 0;
+  text-overflow: ellipsis;
+  overflow: hidden;
+
+  border: 2px solid
+    ${({ theme, fieldHasValueOrFocus }) =>
+      fieldHasValueOrFocus ? theme.colors.brand[1] : theme.colors.border[0]};
+
+  margin: 0;
+  padding: 0.5rem 0.75rem;
+
+  cursor: pointer;
+
+  legend {
+    min-width: 0;
+    max-width: 100%;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+
+    text-transform: none;
+    position: relative;
+    height: 2px;
+    line-height: 0;
+    margin-left: -0.45em;
+    padding: 0 0.5em;
+  }
+
+  &,
+  legend {
+    color: ${({ theme, fieldHasValueOrFocus }) =>
+      fieldHasValueOrFocus && theme.colors.brand[1]};
+  }
+`;
+
+export const DropdownLabelIcon = styled(Icon)`
+  overflow: visible;
+`;
+export const GroupOverflowHidden = styled(Group)`
+  overflow: hidden;
+`;
+
+export const SearchEventSandbox = styled(EventSandbox)`
+  display: contents;
+`;
diff --git a/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.tsx b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1b4e908fb464bd97df712027175ed5ef0093053f
--- /dev/null
+++ b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.tsx
@@ -0,0 +1,163 @@
+/* eslint-disable react/prop-types */
+import { isEmpty } from "underscore";
+import type { MouseEvent } from "react";
+import { useLayoutEffect, useRef, useState } from "react";
+import type {
+  FilterTypeKeys,
+  SearchFilterComponentProps,
+  SearchFilterDropdown,
+  SearchFilterPropTypes,
+} from "metabase/search/types";
+import { Text, Box, Center, Button, Stack } from "metabase/ui";
+import type { IconName } from "metabase/core/components/Icon";
+import { Icon } from "metabase/core/components/Icon";
+import Popover from "metabase/components/Popover";
+import { useSelector } from "metabase/lib/redux";
+import { getIsNavbarOpen } from "metabase/selectors/app";
+import useIsSmallScreen from "metabase/hooks/use-is-small-screen";
+import { isNotNull } from "metabase/core/utils/types";
+import {
+  GroupOverflowHidden,
+  DropdownFieldSet,
+  DropdownLabelIcon,
+  SearchEventSandbox,
+} from "./DropdownSidebarFilter.styled";
+
+export type DropdownSidebarFilterProps<T extends FilterTypeKeys = any> = {
+  filter: SearchFilterDropdown<T>;
+} & SearchFilterComponentProps<T>;
+
+export const DropdownSidebarFilter = ({
+  filter: { label, iconName, DisplayComponent, ContentComponent },
+  "data-testid": dataTestId,
+  value,
+  onChange,
+}: DropdownSidebarFilterProps) => {
+  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+  const isNavbarOpen = useSelector(getIsNavbarOpen);
+  const isSmallScreen = useIsSmallScreen();
+
+  const dropdownRef = useRef<HTMLDivElement>(null);
+  const [popoverWidth, setPopoverWidth] = useState<string>();
+
+  const fieldHasValue = Array.isArray(value)
+    ? !isEmpty(value)
+    : isNotNull(value);
+
+  const handleResize = () => {
+    if (dropdownRef.current) {
+      const { width } = dropdownRef.current.getBoundingClientRect();
+      setPopoverWidth(`${width}px`);
+    }
+  };
+
+  useLayoutEffect(() => {
+    if (!popoverWidth) {
+      handleResize();
+    }
+    window.addEventListener("resize", handleResize, false);
+    return () => window.removeEventListener("resize", handleResize, false);
+  }, [dropdownRef, popoverWidth]);
+
+  useLayoutEffect(() => {
+    if (isNavbarOpen && isSmallScreen) {
+      setIsPopoverOpen(false);
+    }
+  }, [isNavbarOpen, isSmallScreen]);
+
+  const onApplyFilter = (value: SearchFilterPropTypes) => {
+    onChange(value);
+    setIsPopoverOpen(false);
+  };
+
+  const onClearFilter = (e: MouseEvent) => {
+    if (fieldHasValue) {
+      e.stopPropagation();
+      onChange(null);
+      setIsPopoverOpen(false);
+    }
+  };
+
+  const onPopoverClose = () => {
+    setIsPopoverOpen(false);
+  };
+
+  const getDropdownIcon = (): IconName => {
+    if (fieldHasValue) {
+      return "close";
+    } else {
+      return isPopoverOpen ? "chevronup" : "chevrondown";
+    }
+  };
+
+  return (
+    <Box
+      data-testid={dataTestId}
+      ref={dropdownRef}
+      onClick={() => setIsPopoverOpen(!isPopoverOpen)}
+      w="100%"
+      mt={fieldHasValue ? "0.25rem" : 0}
+    >
+      <DropdownFieldSet
+        noPadding
+        legend={fieldHasValue ? label() : null}
+        fieldHasValueOrFocus={fieldHasValue}
+      >
+        <GroupOverflowHidden position="apart" noWrap w="100%">
+          {fieldHasValue ? (
+            <DisplayComponent value={value} />
+          ) : (
+            <GroupOverflowHidden noWrap>
+              {iconName && <DropdownLabelIcon size={16} name={iconName} />}
+              <Text weight={700} truncate>
+                {label()}
+              </Text>
+            </GroupOverflowHidden>
+          )}
+          <Button
+            data-testid="sidebar-filter-dropdown-button"
+            compact
+            mr="0.25rem"
+            size="xs"
+            c="inherit"
+            variant="subtle"
+            onClick={onClearFilter}
+            leftIcon={
+              <Center m="-0.25rem">
+                <Icon size={16} name={getDropdownIcon()} />
+              </Center>
+            }
+          />
+        </GroupOverflowHidden>
+      </DropdownFieldSet>
+
+      <Popover
+        isOpen={isPopoverOpen}
+        onClose={onPopoverClose}
+        target={dropdownRef.current}
+        ignoreTrigger
+        autoWidth
+        sizeToFit
+        pinInitialAttachment
+        horizontalAttachments={["right"]}
+      >
+        {({ maxHeight }: { maxHeight: number }) =>
+          popoverWidth && (
+            <SearchEventSandbox>
+              {popoverWidth && (
+                <Stack mah={maxHeight}>
+                  <ContentComponent
+                    value={value}
+                    onChange={selected => onApplyFilter(selected)}
+                    width={popoverWidth}
+                  />
+                </Stack>
+              )}
+            </SearchEventSandbox>
+          )
+        }
+      </Popover>
+    </Box>
+  );
+};
diff --git a/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.unit.spec.tsx b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.unit.spec.tsx
similarity index 65%
rename from frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.unit.spec.tsx
rename to frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.unit.spec.tsx
index 6d7391fd09d6619851f3e14442fa0086c136d51c..8eacbdf4c193987fcb2d241001f40159adad8403 100644
--- a/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.unit.spec.tsx
+++ b/frontend/src/metabase/search/components/DropdownSidebarFilter/DropdownSidebarFilter.unit.spec.tsx
@@ -2,39 +2,47 @@
 import userEvent from "@testing-library/user-event";
 import { useState } from "react";
 import { renderWithProviders, screen, within } from "__support__/ui";
-import type { SearchSidebarFilterComponent } from "metabase/search/types";
-import type { SearchSidebarFilterProps } from "./SidebarFilter";
-import { SidebarFilter } from "./SidebarFilter";
+import type { SearchFilterComponent } from "metabase/search/types";
+import type { DropdownSidebarFilterProps } from "./DropdownSidebarFilter";
+import { DropdownSidebarFilter } from "./DropdownSidebarFilter";
 
-const mockFilter: SearchSidebarFilterComponent = {
-  title: "Mock Filter",
+const mockFilter: SearchFilterComponent = {
+  label: () => "Mock Filter",
   iconName: "filter",
+  type: "dropdown",
   DisplayComponent: ({ value }) => (
     <div data-testid="mock-display-component">
       {!value || value.length === 0 ? "Display" : value}
     </div>
   ),
-  ContentComponent: ({ value, onChange }) => (
-    <div data-testid="mock-content-component">
-      <button onClick={() => onChange(["new value"])}>Update</button>
-      <div>{value}</div>
-    </div>
-  ),
+  ContentComponent: ({ value, onChange }) => {
+    const [filterValue, setFilterValue] = useState(value);
+
+    return (
+      <div data-testid="mock-content-component">
+        <button onClick={() => setFilterValue(["new value"])}>Update</button>
+        <div>{filterValue}</div>
+        <button onClick={() => onChange(filterValue)}>Apply</button>
+      </div>
+    );
+  },
+  fromUrl: value => value,
+  toUrl: value => value,
 };
 
 const MockSearchSidebarFilter = ({
   filter,
   value,
   onChange,
-}: SearchSidebarFilterProps) => {
+}: DropdownSidebarFilterProps) => {
   const [selectedValues, setSelectedValues] = useState(value);
-  const onFilterChange = (elem: SearchSidebarFilterProps["value"]) => {
+  const onFilterChange = (elem: DropdownSidebarFilterProps["value"]) => {
     setSelectedValues(elem);
     onChange(elem);
   };
 
   return (
-    <SidebarFilter
+    <DropdownSidebarFilter
       filter={filter}
       value={selectedValues}
       onChange={onFilterChange}
@@ -42,14 +50,14 @@ const MockSearchSidebarFilter = ({
   );
 };
 
-const setup = (options: Partial<SearchSidebarFilterProps> = {}) => {
-  const defaultProps: SearchSidebarFilterProps = {
+const setup = (options: Partial<DropdownSidebarFilterProps> = {}) => {
+  const defaultProps: DropdownSidebarFilterProps = {
     filter: mockFilter,
     value: [],
     onChange: jest.fn(),
   };
 
-  const props: SearchSidebarFilterProps = { ...defaultProps, ...options };
+  const props: DropdownSidebarFilterProps = { ...defaultProps, ...options };
 
   renderWithProviders(<MockSearchSidebarFilter {...props} />);
 
@@ -58,7 +66,7 @@ const setup = (options: Partial<SearchSidebarFilterProps> = {}) => {
   };
 };
 
-describe("SidebarFilter", () => {
+describe("DropdownSidebarFilter", () => {
   it("should render filter title, filter icon, chevrondown, but no legend text when no value is selected", () => {
     setup();
 
@@ -73,7 +81,7 @@ describe("SidebarFilter", () => {
     setup({ value: ["value1"] });
 
     expect(screen.getByTestId("field-set-legend")).toHaveTextContent(
-      mockFilter.title,
+      mockFilter.label(),
     );
 
     expect(screen.getByTestId("mock-display-component")).toBeInTheDocument();
@@ -97,7 +105,8 @@ describe("SidebarFilter", () => {
     userEvent.click(screen.getByRole("button", { name: "Update" }));
     expect(screen.getByText("new value")).toBeInTheDocument();
 
-    userEvent.click(screen.getByRole("button", { name: "Apply filters" }));
+    userEvent.click(screen.getByRole("button", { name: "Apply" }));
+
     expect(onChange).toHaveBeenCalledWith(["new value"]);
 
     expect(screen.getByTestId("mock-display-component")).toHaveTextContent(
@@ -112,22 +121,33 @@ describe("SidebarFilter", () => {
   it("should revert filter selections when popover is closed", async () => {
     const { onChange } = setup({ value: ["old value"] });
 
-    userEvent.click(screen.getByText("Mock Filter"));
+    userEvent.click(screen.getByText("old value"));
     userEvent.click(screen.getByRole("button", { name: "Update" }));
     expect(screen.getByText("new value")).toBeInTheDocument();
 
-    userEvent.click(screen.getByText("Mock Filter"));
+    userEvent.click(screen.getByText("old value"));
     expect(onChange).not.toHaveBeenCalled();
     expect(screen.getByTestId("mock-display-component")).toHaveTextContent(
       "old value",
     );
+
+    userEvent.click(screen.getByText("old value"));
+    expect(screen.getByTestId("mock-display-component")).toHaveTextContent(
+      "old value",
+    );
   });
 
   it("should reset filter selections when clear button is clicked", async () => {
     const { onChange } = setup({ value: ["old value"] });
-    userEvent.click(screen.getByTestId("sidebar-filter-dropdown-button"));
+    userEvent.click(
+      screen.getByTestId("sidebar-filter-dropdown-button"),
+      undefined,
+      // There's a problem with buttons in fieldsets so we have to skip pointer events check for now
+      // https://github.com/testing-library/user-event/issues/662
+      { skipPointerEventsCheck: true },
+    );
 
-    expect(onChange).toHaveBeenCalledWith(undefined);
+    expect(onChange).toHaveBeenCalledWith(null);
     expect(screen.getByText("Mock Filter")).toBeInTheDocument();
     expect(screen.getByLabelText("filter icon")).toBeInTheDocument();
     expect(screen.getByLabelText("chevrondown icon")).toBeInTheDocument();
diff --git a/frontend/src/metabase/search/components/DropdownSidebarFilter/index.ts b/frontend/src/metabase/search/components/DropdownSidebarFilter/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1b997ef19ce4380f3a1b976168bffb9ebb53387e
--- /dev/null
+++ b/frontend/src/metabase/search/components/DropdownSidebarFilter/index.ts
@@ -0,0 +1 @@
+export { DropdownSidebarFilter } from "./DropdownSidebarFilter";
diff --git a/frontend/src/metabase/search/components/InfoText.tsx b/frontend/src/metabase/search/components/InfoText.tsx
deleted file mode 100644
index 78cc2530a8936698fbbcc554034e7ee508283484..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/search/components/InfoText.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { t, jt } from "ttag";
-
-import * as Urls from "metabase/lib/urls";
-
-import { Icon } from "metabase/core/components/Icon";
-import Link from "metabase/core/components/Link";
-
-import Schema from "metabase/entities/schemas";
-import Database from "metabase/entities/databases";
-import Table from "metabase/entities/tables";
-import { PLUGIN_COLLECTIONS } from "metabase/plugins";
-import { getTranslatedEntityName } from "metabase/common/utils/model-names";
-
-import type { Collection } from "metabase-types/api";
-import type { WrappedResult } from "metabase/search/types";
-import type TableType from "metabase-lib/metadata/Table";
-
-import { CollectionBadge } from "./CollectionBadge";
-
-export function InfoText({ result }: { result: WrappedResult }) {
-  let textContent: string | string[] | JSX.Element;
-
-  switch (result.model) {
-    case "card":
-      textContent = jt`Saved question in ${formatCollection(
-        result,
-        result.getCollection(),
-      )}`;
-      break;
-    case "dataset":
-      textContent = jt`Model in ${formatCollection(
-        result,
-        result.getCollection(),
-      )}`;
-      break;
-    case "collection":
-      textContent = getCollectionInfoText(result.collection);
-      break;
-    case "database":
-      textContent = t`Database`;
-      break;
-    case "table":
-      textContent = <TablePath result={result} />;
-      break;
-    case "segment":
-      textContent = jt`Segment of ${(<TableLink result={result} />)}`;
-      break;
-    case "metric":
-      textContent = jt`Metric for ${(<TableLink result={result} />)}`;
-      break;
-    case "action":
-      textContent = jt`for ${result.model_name}`;
-      break;
-    case "indexed-entity":
-      textContent = jt`in ${result.model_name}`;
-      break;
-    default:
-      textContent = jt`${getTranslatedEntityName(
-        result.model,
-      )} in ${formatCollection(result, result.getCollection())}`;
-      break;
-  }
-
-  return <>{textContent}</>;
-}
-
-function formatCollection(
-  result: WrappedResult,
-  collection: Partial<Collection>,
-) {
-  return (
-    collection.id && (
-      <CollectionBadge key={result.model} collection={collection} />
-    )
-  );
-}
-
-function getCollectionInfoText(collection: Partial<Collection>) {
-  if (
-    PLUGIN_COLLECTIONS.isRegularCollection(collection) ||
-    !collection.authority_level
-  ) {
-    return t`Collection`;
-  }
-  const level = PLUGIN_COLLECTIONS.AUTHORITY_LEVEL[collection.authority_level];
-  return `${level.name} ${t`Collection`}`;
-}
-
-function TablePath({ result }: { result: WrappedResult }) {
-  return (
-    <>
-      {jt`Table in ${(
-        <span key="table-path">
-          <Database.Link id={result.database_id} />{" "}
-          {result.table_schema && (
-            <Schema.ListLoader
-              query={{ dbId: result.database_id }}
-              loadingAndErrorWrapper={false}
-            >
-              {({ list }: { list: typeof Schema[] }) =>
-                list?.length > 1 ? (
-                  <span>
-                    <Icon name="chevronright" size={10} />
-                    {/* we have to do some {} manipulation here to make this look like the table object that browseSchema was written for originally */}
-                    <Link
-                      to={Urls.browseSchema({
-                        db: { id: result.database_id },
-                        schema_name: result.table_schema,
-                      } as TableType)}
-                    >
-                      {result.table_schema}
-                    </Link>
-                  </span>
-                ) : null
-              }
-            </Schema.ListLoader>
-          )}
-        </span>
-      )}`}
-    </>
-  );
-}
-
-function TableLink({ result }: { result: WrappedResult }) {
-  return (
-    <Link to={Urls.tableRowsQuery(result.database_id, result.table_id)}>
-      <Table.Loader id={result.table_id} loadingAndErrorWrapper={false}>
-        {({ table }: { table: TableType }) =>
-          table ? <span>{table.display_name}</span> : null
-        }
-      </Table.Loader>
-    </Link>
-  );
-}
diff --git a/frontend/src/metabase/search/components/InfoText.unit.spec.js b/frontend/src/metabase/search/components/InfoText.unit.spec.js
deleted file mode 100644
index 918422c06c259aa3793392d662d71c6bbb1581c0..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/search/components/InfoText.unit.spec.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import fetchMock from "fetch-mock";
-import { renderWithProviders, screen } from "__support__/ui";
-
-import { InfoText } from "./InfoText";
-
-const collection = { id: 1, name: "Collection Name" };
-const table = { id: 1, display_name: "Table Name" };
-const database = { id: 1, name: "Database Name" };
-
-async function setup(result) {
-  fetchMock.get("path:/api/table/1", table);
-  fetchMock.get("path:/api/database/1", database);
-
-  renderWithProviders(<InfoText result={result} />);
-}
-
-describe("InfoText", () => {
-  it("shows collection info for a question", async () => {
-    await setup({
-      model: "card",
-      getCollection: () => collection,
-    });
-    expect(screen.getByText("Saved question in")).toHaveTextContent(
-      "Saved question in Collection Name",
-    );
-  });
-
-  it("shows collection info for a collection", async () => {
-    const collection = { id: 1, name: "Collection Name" };
-    await setup({
-      model: "collection",
-      collection,
-    });
-    expect(screen.getByText("Collection")).toBeInTheDocument();
-  });
-
-  it("shows Database for databases", async () => {
-    await setup({
-      model: "database",
-    });
-    expect(screen.getByText("Database")).toBeInTheDocument();
-  });
-
-  it("shows segment's table name", async () => {
-    await setup({
-      model: "segment",
-      table_id: 1,
-      database_id: 1,
-    });
-
-    expect(await screen.findByText("Table Name")).toBeInTheDocument();
-    expect(await screen.findByText("Segment of")).toHaveTextContent(
-      "Segment of Table Name",
-    );
-  });
-
-  it("shows metric's table name", async () => {
-    await setup({
-      model: "metric",
-      table_id: 1,
-      database_id: 1,
-    });
-
-    expect(await screen.findByText("Table Name")).toBeInTheDocument();
-    expect(await screen.findByText("Metric for")).toHaveTextContent(
-      "Metric for Table Name",
-    );
-  });
-
-  it("shows table's schema", async () => {
-    await setup({
-      model: "table",
-      table_id: 1,
-      database_id: 1,
-    });
-
-    expect(await screen.findByText("Database Name")).toBeInTheDocument();
-    expect(await screen.findByText("Table in")).toHaveTextContent(
-      "Table in Database Name",
-    );
-  });
-
-  it("shows pulse's collection", async () => {
-    await setup({
-      model: "pulse",
-      getCollection: () => collection,
-    });
-
-    expect(screen.getByText("Pulse in")).toHaveTextContent(
-      "Pulse in Collection Name",
-    );
-  });
-
-  it("shows dashboard's collection", async () => {
-    await setup({
-      model: "dashboard",
-      getCollection: () => collection,
-    });
-
-    expect(screen.getByText("Dashboard in")).toHaveTextContent(
-      "Dashboard in Collection Name",
-    );
-  });
-});
diff --git a/frontend/src/metabase/search/components/InfoText/InfoText.styled.tsx b/frontend/src/metabase/search/components/InfoText/InfoText.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..80a8dfab84ad895a7db0f51410ac3dae9801cb5d
--- /dev/null
+++ b/frontend/src/metabase/search/components/InfoText/InfoText.styled.tsx
@@ -0,0 +1,32 @@
+import { css } from "@emotion/react";
+import styled from "@emotion/styled";
+import LastEditInfoLabel from "metabase/components/LastEditInfoLabel";
+import { color } from "metabase/lib/colors";
+import { breakpointMaxSmall } from "metabase/styled-components/theme";
+
+export const LastEditedInfoText = styled(LastEditInfoLabel)`
+  ${({ theme }) => {
+    return css`
+      color: ${theme.colors.text[1]};
+      font-size: ${theme.fontSizes.sm};
+      font-weight: 500;
+
+      cursor: pointer;
+
+      &:hover {
+        color: ${theme.colors.brand[1]};
+      }
+    `;
+  }}
+  ${breakpointMaxSmall} {
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+
+    max-width: 50%;
+  }
+`;
+
+export const LastEditedInfoTooltip = styled(LastEditInfoLabel)`
+  color: ${color("white")};
+`;
diff --git a/frontend/src/metabase/search/components/InfoText/InfoText.tsx b/frontend/src/metabase/search/components/InfoText/InfoText.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..01a83b5a1cc026c87a63504b79df5e5d47a79908
--- /dev/null
+++ b/frontend/src/metabase/search/components/InfoText/InfoText.tsx
@@ -0,0 +1,209 @@
+/* eslint-disable react/prop-types */
+import moment from "moment-timezone";
+import { t } from "ttag";
+import { isNull } from "underscore";
+import { useDatabaseQuery, useTableQuery } from "metabase/common/hooks";
+import {
+  browseDatabase,
+  browseSchema,
+  tableRowsQuery,
+} from "metabase/lib/urls";
+import Tooltip from "metabase/core/components/Tooltip";
+import { getRelativeTime } from "metabase/lib/time";
+import { isNotNull } from "metabase/core/utils/types";
+import type { UserListResult } from "metabase-types/api";
+import { useUserListQuery } from "metabase/common/hooks/use-user-list-query";
+import { Icon } from "metabase/core/components/Icon";
+import { SearchResultLink } from "metabase/search/components/SearchResultLink";
+import type { WrappedResult } from "metabase/search/types";
+import { Group, Box, Text } from "metabase/ui";
+import type Database from "metabase-lib/metadata/Database";
+import type { InfoTextData } from "./get-info-text";
+import { getInfoText } from "./get-info-text";
+import { LastEditedInfoText, LastEditedInfoTooltip } from "./InfoText.styled";
+
+export type InfoTextProps = {
+  result: WrappedResult;
+  isCompact?: boolean;
+};
+
+const LinkSeparator = (
+  <Box component="span" c="text.1">
+    <Icon name="chevronright" size={8} />
+  </Box>
+);
+
+const InfoTextSeparator = (
+  <Text span size="sm" mx="xs" c="text.1">
+    •
+  </Text>
+);
+
+const LoadingText = ({ "data-testid": dataTestId = "loading-text" }) => (
+  <Text
+    color="text-1"
+    span
+    size="sm"
+    truncate
+    data-testid={dataTestId}
+  >{t`Loading…`}</Text>
+);
+
+export const InfoTextTableLink = ({ result }: InfoTextProps) => {
+  const { data: table, isLoading } = useTableQuery({
+    id: result.table_id,
+  });
+
+  const link = tableRowsQuery(result.database_id, result.table_id);
+  const label = table?.display_name ?? null;
+
+  if (isLoading) {
+    return <LoadingText data-testid="info-text-asset-link-loading-text" />;
+  }
+
+  return (
+    <SearchResultLink key={label} href={link}>
+      {label}
+    </SearchResultLink>
+  );
+};
+
+export const DatabaseLink = ({ database }: { database: Database }) => (
+  <SearchResultLink key={database.name} href={browseDatabase(database)}>
+    {database.name}
+  </SearchResultLink>
+);
+export const TableLink = ({ result }: { result: WrappedResult }) => {
+  const link = browseSchema({
+    db: { id: result.database_id },
+    schema_name: result.table_schema,
+  });
+
+  return (
+    <>
+      <SearchResultLink key={result.table_schema} href={link}>
+        {result.table_schema}
+      </SearchResultLink>
+    </>
+  );
+};
+
+export const InfoTextTablePath = ({ result }: InfoTextProps) => {
+  const { data: database, isLoading: isDatabaseLoading } = useDatabaseQuery({
+    id: result.database_id,
+  });
+
+  const showDatabaseLink =
+    !isDatabaseLoading && database && database.name !== null;
+  const showTableLink = showDatabaseLink && !!result.table_schema;
+
+  if (isDatabaseLoading) {
+    return <LoadingText data-testid="info-text-asset-link-loading-text" />;
+  }
+
+  return (
+    <>
+      {showDatabaseLink && <DatabaseLink database={database} />}
+      {showTableLink && (
+        <>
+          {LinkSeparator}
+          <TableLink result={result} />
+        </>
+      )}
+    </>
+  );
+};
+
+export const InfoTextAssetLink = ({ result }: InfoTextProps) => {
+  if (result.model === "table") {
+    return <InfoTextTablePath result={result} />;
+  }
+
+  if (result.model === "segment" || result.model === "metric") {
+    return <InfoTextTableLink result={result} />;
+  }
+
+  const { label, link, icon }: InfoTextData = getInfoText(result);
+
+  return label ? (
+    <SearchResultLink key={label} href={link} leftIcon={icon}>
+      {label}
+    </SearchResultLink>
+  ) : null;
+};
+
+export const InfoTextEditedInfo = ({ result, isCompact }: InfoTextProps) => {
+  const { data: users = [], isLoading } = useUserListQuery();
+
+  const isUpdated =
+    isNotNull(result.last_edited_at) &&
+    !moment(result.last_edited_at).isSame(result.created_at, "seconds");
+
+  const { prefix, timestamp, userId } = isUpdated
+    ? {
+        prefix: t`Updated`,
+        timestamp: result.last_edited_at,
+        userId: result.last_editor_id,
+      }
+    : {
+        prefix: t`Created`,
+        timestamp: result.created_at,
+        userId: result.creator_id,
+      };
+
+  const user = users.find((user: UserListResult) => user.id === userId);
+
+  const lastEditedInfoData = {
+    item: {
+      "last-edit-info": {
+        id: user?.id,
+        email: user?.email,
+        first_name: user?.first_name,
+        last_name: user?.last_name,
+        timestamp,
+      },
+    },
+    prefix,
+  };
+
+  if (isLoading) {
+    return (
+      <>
+        {InfoTextSeparator}
+        <LoadingText data-testid="last-edited-info-loading-text" />
+      </>
+    );
+  }
+
+  if (isNull(timestamp) && isNull(userId)) {
+    return null;
+  }
+
+  const getEditedInfoText = () => {
+    if (isCompact) {
+      const formattedDuration = timestamp && getRelativeTime(timestamp);
+      return (
+        <Tooltip tooltip={<LastEditedInfoTooltip {...lastEditedInfoData} />}>
+          <Text span size="sm" c="text.1" truncate>
+            {formattedDuration}
+          </Text>
+        </Tooltip>
+      );
+    }
+    return <LastEditedInfoText {...lastEditedInfoData} />;
+  };
+
+  return (
+    <>
+      {InfoTextSeparator}
+      {getEditedInfoText()}
+    </>
+  );
+};
+
+export const InfoText = ({ result, isCompact }: InfoTextProps) => (
+  <Group noWrap spacing="xs">
+    <InfoTextAssetLink result={result} />
+    <InfoTextEditedInfo result={result} isCompact={isCompact} />
+  </Group>
+);
diff --git a/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx b/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5bcc0c6dda38cf8a4cf02658d0ae96b80ebcbdfb
--- /dev/null
+++ b/frontend/src/metabase/search/components/InfoText/InfoText.unit.spec.tsx
@@ -0,0 +1,350 @@
+import { waitFor } from "@testing-library/react";
+import moment from "moment";
+import {
+  setupCollectionByIdEndpoint,
+  setupDatabaseEndpoints,
+  setupTableEndpoints,
+  setupUsersEndpoints,
+} from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import type { SearchModelType, SearchResult } from "metabase-types/api";
+import {
+  createMockCollection,
+  createMockDatabase,
+  createMockSearchResult,
+  createMockTable,
+  createMockUser,
+} from "metabase-types/api/mocks";
+import type { IconName } from "metabase/core/components/Icon";
+import type { WrappedResult } from "metabase/search/types";
+import { InfoText } from "./InfoText";
+
+const MOCK_COLLECTION = createMockCollection({
+  id: 1,
+  name: "Collection Name",
+});
+const MOCK_TABLE = createMockTable({ id: 1, display_name: "Table Name" });
+const MOCK_DATABASE = createMockDatabase({ id: 1, name: "Database Name" });
+const MOCK_USER = createMockUser();
+const MOCK_OTHER_USER = createMockUser({
+  id: 2,
+  first_name: "John",
+  last_name: "Cena",
+  common_name: "John Cena",
+});
+
+const CREATED_AT_TIME = "2022-01-01T00:00:00.000Z";
+const LAST_EDITED_TIME = "2023-01-01T00:00:00.000Z";
+const formatDuration = (timestamp: string) =>
+  moment.duration(moment().diff(moment(timestamp))).humanize();
+
+const CREATED_AT_DURATION = formatDuration(CREATED_AT_TIME);
+const LAST_EDITED_DURATION = formatDuration(LAST_EDITED_TIME);
+
+const createSearchResult = ({
+  model,
+  ...resultProps
+}: {
+  model: SearchModelType;
+} & Partial<SearchResult>) =>
+  createMockSearchResult({
+    collection: MOCK_COLLECTION,
+    database_id: MOCK_DATABASE.id,
+    created_at: CREATED_AT_TIME,
+    creator_common_name: MOCK_USER.common_name,
+    creator_id: MOCK_USER.id,
+    last_edited_at: LAST_EDITED_TIME,
+    last_editor_common_name: MOCK_OTHER_USER.common_name,
+    last_editor_id: MOCK_OTHER_USER.id,
+    model,
+    ...resultProps,
+  });
+
+async function setup({
+  model = "card",
+  isCompact = false,
+  resultProps = {},
+}: {
+  model?: SearchModelType;
+  isCompact?: boolean;
+  resultProps?: Partial<SearchResult>;
+} = {}) {
+  setupTableEndpoints(MOCK_TABLE);
+  setupDatabaseEndpoints(MOCK_DATABASE);
+  setupUsersEndpoints([MOCK_USER, MOCK_OTHER_USER]);
+  setupCollectionByIdEndpoint({
+    collections: [MOCK_COLLECTION],
+  });
+
+  const result = createSearchResult({ model, ...resultProps });
+
+  const getUrl = jest.fn(() => "a/b/c");
+  const getIcon = jest.fn(() => ({
+    name: "eye" as IconName,
+    size: 14,
+    width: 14,
+    height: 14,
+  }));
+  const getCollection = jest.fn(() => result.collection);
+
+  const wrappedResult: WrappedResult = {
+    ...result,
+    getUrl,
+    getIcon,
+    getCollection,
+  };
+
+  renderWithProviders(
+    <InfoText result={wrappedResult} isCompact={isCompact} />,
+  );
+
+  await waitFor(() =>
+    expect(
+      screen.queryByTestId("info-text-asset-link-loading-text"),
+    ).not.toBeInTheDocument(),
+  );
+
+  // await waitforAssetLinkLoadingTextToBeRemoved()
+  await waitForLoadingTextToBeRemoved();
+
+  return {
+    getUrl,
+    getIcon,
+    getCollection,
+  };
+}
+
+describe("InfoText", () => {
+  describe("showing relevant information for each model type", () => {
+    it("shows collection info for a question", async () => {
+      await setup({
+        model: "card",
+      });
+
+      const collectionLink = screen.getByText("Collection Name");
+      expect(collectionLink).toBeInTheDocument();
+      expect(collectionLink).toHaveAttribute(
+        "href",
+        `/collection/${MOCK_COLLECTION.id}-collection-name`,
+      );
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows collection info for a collection", async () => {
+      await setup({
+        model: "collection",
+      });
+      const collectionElement = screen.getByText("Collection");
+      expect(collectionElement).toBeInTheDocument();
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows Database for databases", async () => {
+      await setup({
+        model: "database",
+      });
+      expect(screen.getByText("Database")).toBeInTheDocument();
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows segment's table name", async () => {
+      await setup({
+        model: "segment",
+      });
+
+      const tableLink = screen.getByText(MOCK_TABLE.display_name);
+      expect(tableLink).toHaveAttribute(
+        "href",
+        `/question#?db=${MOCK_DATABASE.id}&table=${MOCK_TABLE.id}`,
+      );
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows metric's table name", async () => {
+      await setup({
+        model: "metric",
+      });
+
+      const tableLink = screen.getByText(MOCK_TABLE.display_name);
+      expect(tableLink).toHaveAttribute(
+        "href",
+        `/question#?db=${MOCK_DATABASE.id}&table=${MOCK_TABLE.id}`,
+      );
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows table's schema", async () => {
+      await setup({
+        model: "table",
+      });
+
+      const databaseLink = screen.getByText("Database Name");
+      expect(databaseLink).toBeInTheDocument();
+      expect(databaseLink).toHaveAttribute(
+        "href",
+        `/browse/${MOCK_DATABASE.id}-database-name`,
+      );
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows pulse's collection", async () => {
+      await setup({
+        model: "pulse",
+      });
+
+      const collectionLink = screen.getByText("Collection Name");
+      expect(collectionLink).toBeInTheDocument();
+      expect(collectionLink).toHaveAttribute(
+        "href",
+        `/collection/${MOCK_COLLECTION.id}-collection-name`,
+      );
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("shows dashboard's collection", async () => {
+      await setup({
+        model: "dashboard",
+      });
+
+      const collectionLink = screen.getByText("Collection Name");
+      expect(collectionLink).toBeInTheDocument();
+      expect(collectionLink).toHaveAttribute(
+        "href",
+        `/collection/${MOCK_COLLECTION.id}-collection-name`,
+      );
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+  });
+
+  describe("showing last_edited_by vs created_by", () => {
+    it("should show last_edited_by when available", async () => {
+      await setup();
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION} ago by ${MOCK_OTHER_USER.common_name}`,
+      );
+    });
+
+    it("should show created_by when last_edited_at is not available", async () => {
+      await setup({
+        resultProps: {
+          last_edited_at: null,
+          last_editor_id: null,
+          created_at: null,
+        },
+      });
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Created by you`,
+      );
+    });
+
+    it("should not show user when neither last_edited_by and created_by are available", async () => {
+      await setup({
+        resultProps: {
+          last_editor_id: null,
+          last_edited_at: null,
+          creator_id: null,
+        },
+      });
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Created ${CREATED_AT_DURATION} ago`,
+      );
+    });
+
+    it("should not show user when last_edited_by isn't available but last_edited_at is", async () => {
+      await setup({
+        resultProps: {
+          last_editor_id: null,
+          creator_id: null,
+        },
+      });
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION} ago`,
+      );
+    });
+  });
+
+  describe("showing last_edited_at vs created_at", () => {
+    it("should show last_edited_at time when available", async () => {
+      await setup({
+        resultProps: {
+          last_editor_id: null,
+        },
+      });
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Updated ${LAST_EDITED_DURATION}`,
+      );
+    });
+
+    it("should show created time when last_edited_at is not available", async () => {
+      await setup({
+        resultProps: {
+          last_edited_at: null,
+          last_editor_id: null,
+          creator_id: null,
+        },
+      });
+
+      expect(screen.getByTestId("revision-history-button")).toHaveTextContent(
+        `Created ${CREATED_AT_DURATION}`,
+      );
+    });
+
+    it("should not show timestamp if neither last_edited_at or created_at is available", async () => {
+      await setup({
+        resultProps: {
+          last_edited_at: null,
+          last_editor_id: null,
+          created_at: null,
+          creator_id: null,
+        },
+      });
+
+      expect(screen.queryByText("•")).not.toBeInTheDocument();
+      expect(
+        screen.queryByTestId("revision-history-button"),
+      ).not.toBeInTheDocument();
+    });
+  });
+});
+
+async function waitForLoadingTextToBeRemoved() {
+  await waitFor(() => {
+    expect(
+      screen.queryByTestId("info-text-asset-link-loading-text"),
+    ).not.toBeInTheDocument();
+  });
+
+  await waitFor(() => {
+    expect(
+      screen.queryByTestId("last-edited-info-loading-text"),
+    ).not.toBeInTheDocument();
+  });
+}
diff --git a/frontend/src/metabase/search/components/InfoText/get-info-text.tsx b/frontend/src/metabase/search/components/InfoText/get-info-text.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..31a361fb4ef90f2456af25b2e6f7f40f798d72a4
--- /dev/null
+++ b/frontend/src/metabase/search/components/InfoText/get-info-text.tsx
@@ -0,0 +1,77 @@
+import { t } from "ttag";
+import {
+  PLUGIN_COLLECTION_COMPONENTS,
+  PLUGIN_COLLECTIONS,
+} from "metabase/plugins";
+import type { WrappedResult } from "metabase/search/types";
+import type { Collection } from "metabase-types/api";
+import { collection as collectionUrl } from "metabase/lib/urls";
+
+import { Box } from "metabase/ui";
+
+const { CollectionAuthorityLevelIcon } = PLUGIN_COLLECTION_COMPONENTS;
+
+export type InfoTextData = {
+  link?: string | null;
+  icon?: JSX.Element | null;
+  label?: string | null;
+};
+
+export const getInfoText = (result: WrappedResult): InfoTextData => {
+  switch (result.model) {
+    case "collection":
+      return getCollectionInfoText(result);
+    case "database":
+      return getDatabaseInfoText();
+    case "action":
+      return getActionInfoText(result);
+    case "card":
+    case "dataset":
+    case "indexed-entity":
+    default:
+      return getCollectionResult(result);
+  }
+};
+const getActionInfoText = (result: WrappedResult): InfoTextData => {
+  return {
+    label: result.model_name,
+  };
+};
+const getDatabaseInfoText = (): InfoTextData => {
+  return {
+    label: t`Database`,
+  };
+};
+const getCollectionInfoText = (result: WrappedResult): InfoTextData => {
+  const collection: Partial<Collection> = result.getCollection();
+
+  if (
+    PLUGIN_COLLECTIONS.isRegularCollection(collection) ||
+    !collection.authority_level
+  ) {
+    return {
+      label: t`Collection`,
+    };
+  }
+  const level = PLUGIN_COLLECTIONS.AUTHORITY_LEVEL[collection.authority_level];
+  return {
+    label: `${level.name} ${t`Collection`}`,
+  };
+};
+
+const getCollectionResult = (result: WrappedResult): InfoTextData => {
+  const collection = result.getCollection();
+  const colUrl = collectionUrl(collection);
+  const collectionName = collection.name;
+  return collectionName
+    ? {
+        icon: collection.authority_level ? (
+          <Box ml="-1.5px" display="inherit" pos="relative" top="-0.5px">
+            <CollectionAuthorityLevelIcon size={12} collection={collection} />
+          </Box>
+        ) : null,
+        link: colUrl,
+        label: collectionName,
+      }
+    : {};
+};
diff --git a/frontend/src/metabase/search/components/InfoText/index.ts b/frontend/src/metabase/search/components/InfoText/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..49aef2d70538107fab1fd0b9357278dc2eb2b4db
--- /dev/null
+++ b/frontend/src/metabase/search/components/InfoText/index.ts
@@ -0,0 +1 @@
+export * from "./InfoText";
diff --git a/frontend/src/metabase/search/components/SearchFilterDateDisplay/SearchFilterDateDisplay.tsx b/frontend/src/metabase/search/components/SearchFilterDateDisplay/SearchFilterDateDisplay.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4570b3f576ea08df969668d26183161d283106c4
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterDateDisplay/SearchFilterDateDisplay.tsx
@@ -0,0 +1,20 @@
+import { Text } from "metabase/ui";
+import { getFilterTitle } from "metabase/parameters/utils/date-formatting";
+import { dateParameterValueToMBQL } from "metabase-lib/parameters/utils/mbql";
+
+export type SearchFilterDateDisplayProps = {
+  label: string;
+  value: string | null;
+};
+export const SearchFilterDateDisplay = ({
+  label,
+  value,
+}: SearchFilterDateDisplayProps) => {
+  const dateFilter = dateParameterValueToMBQL(value, null);
+
+  return (
+    <Text c="inherit" fw={700} truncate>
+      {dateFilter ? getFilterTitle(dateFilter) : label}
+    </Text>
+  );
+};
diff --git a/frontend/src/metabase/search/components/SearchFilterDateDisplay/SearchFilterDateDisplay.unit.spec.tsx b/frontend/src/metabase/search/components/SearchFilterDateDisplay/SearchFilterDateDisplay.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1917b354bbdaee50e124c89504447b2bec31ee4d
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterDateDisplay/SearchFilterDateDisplay.unit.spec.tsx
@@ -0,0 +1,36 @@
+import { render, screen } from "@testing-library/react";
+import type { SearchFilterDateDisplayProps } from "./SearchFilterDateDisplay";
+import { SearchFilterDateDisplay } from "./SearchFilterDateDisplay";
+
+const TEST_TITLE = "SearchFilterDateDisplay Title";
+
+const TEST_DATE_FILTER_DISPLAY: [string | null | undefined, string][] = [
+  ["thisday", "Today"],
+  ["past1days", "Yesterday"],
+  ["past1weeks", "Previous Week"],
+  ["past7days", "Previous 7 Days"],
+  ["past30days", "Previous 30 Days"],
+  ["past1months", "Previous Month"],
+  ["past3months", "Previous 3 Months"],
+  ["past12months", "Previous 12 Months"],
+  ["2023-08-30~2023-09-29", "August 30, 2023 - September 29, 2023"],
+  ["past123quarters", "Previous 123 Quarters"],
+  ["invalidSuperString", TEST_TITLE],
+  [null, TEST_TITLE],
+  [undefined, TEST_TITLE],
+];
+const setup = ({
+  value = null,
+}: Partial<SearchFilterDateDisplayProps> = {}) => {
+  render(<SearchFilterDateDisplay label={TEST_TITLE} value={value} />);
+};
+
+describe("SearchFilterDateDisplay", () => {
+  it.each(TEST_DATE_FILTER_DISPLAY)(
+    "displays correct title when value is %s",
+    (value, title) => {
+      setup({ value });
+      expect(screen.getByText(title)).toBeInTheDocument();
+    },
+  );
+});
diff --git a/frontend/src/metabase/search/components/SearchFilterDateDisplay/index.ts b/frontend/src/metabase/search/components/SearchFilterDateDisplay/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d15cf4e30a1f27c75dbcd99954746f0de312aae5
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterDateDisplay/index.ts
@@ -0,0 +1 @@
+export * from "./SearchFilterDateDisplay";
diff --git a/frontend/src/metabase/search/components/SearchFilterDatePicker/SearchFilterDatePicker.tsx b/frontend/src/metabase/search/components/SearchFilterDatePicker/SearchFilterDatePicker.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..37ec445abb2c6804839735d12244c8ed4d7515f7
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterDatePicker/SearchFilterDatePicker.tsx
@@ -0,0 +1,42 @@
+import { useState } from "react";
+import { t } from "ttag";
+import { SearchFilterApplyButton } from "metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper";
+import { filterToUrlEncoded } from "metabase/parameters/utils/date-formatting";
+import DatePicker from "metabase/query_builder/components/filters/pickers/DatePicker/DatePicker";
+import type { DateShortcutOptions } from "metabase/query_builder/components/filters/pickers/DatePicker/DatePickerShortcutOptions";
+import { DATE_SHORTCUT_OPTIONS } from "metabase/query_builder/components/filters/pickers/DatePicker/DatePickerShortcutOptions";
+import { dateParameterValueToMBQL } from "metabase-lib/parameters/utils/mbql";
+
+const CREATED_AT_SHORTCUTS: DateShortcutOptions = {
+  ...DATE_SHORTCUT_OPTIONS,
+  MISC_OPTIONS: DATE_SHORTCUT_OPTIONS.MISC_OPTIONS.filter(
+    ({ displayName }) => displayName !== t`Exclude...`,
+  ),
+};
+
+export const SearchFilterDatePicker = ({
+  value,
+  onChange,
+}: {
+  value: string | null;
+  onChange: (value: string | null) => void;
+}) => {
+  const [filter, onFilterChange] = useState(
+    dateParameterValueToMBQL(value) ?? [],
+  );
+
+  const onCommit = (filterToCommit: any[]) => {
+    onChange(filterToUrlEncoded(filterToCommit));
+  };
+
+  return (
+    <DatePicker
+      filter={filter}
+      onCommit={onCommit}
+      onFilterChange={f => onFilterChange(f)}
+      dateShortcutOptions={CREATED_AT_SHORTCUTS}
+    >
+      <SearchFilterApplyButton onApply={() => onCommit(filter)} />
+    </DatePicker>
+  );
+};
diff --git a/frontend/src/metabase/search/components/SearchFilterDatePicker/SearchFilterDatePicker.unit.spec.tsx b/frontend/src/metabase/search/components/SearchFilterDatePicker/SearchFilterDatePicker.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c42e9e4baeb4138e8991e76b61329469e362430a
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterDatePicker/SearchFilterDatePicker.unit.spec.tsx
@@ -0,0 +1,53 @@
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders, screen, within } from "__support__/ui";
+import { SearchFilterDatePicker } from "./SearchFilterDatePicker";
+
+type SetupProps = {
+  value?: string | null;
+};
+
+const setup = ({ value = null }: SetupProps = {}) => {
+  const onChangeMock = jest.fn();
+  renderWithProviders(
+    <SearchFilterDatePicker value={value} onChange={onChangeMock} />,
+  );
+  return {
+    onChangeMock,
+  };
+};
+
+describe("SearchFilterDatePicker", () => {
+  it("should render SearchFilterDatePicker component", () => {
+    setup();
+    expect(screen.getByTestId("date-picker")).toBeInTheDocument();
+  });
+
+  it("should not display Exclude… in the date picker shortcut options", () => {
+    setup();
+    expect(screen.queryByText("Exclude…")).not.toBeInTheDocument();
+  });
+
+  it("should call onChange when a date is selected", () => {
+    const { onChangeMock } = setup();
+    userEvent.click(screen.getByText("Today"));
+    expect(onChangeMock).toHaveBeenCalled();
+  });
+
+  it("should populate the `Specific dates…` date picker with the value passed in", () => {
+    setup({ value: "2023-09-20" });
+    const specificDatePicker = screen.getByTestId("specific-date-picker");
+    expect(specificDatePicker).toBeInTheDocument();
+
+    expect(
+      within(screen.getByTestId("specific-date-picker")).getByRole("textbox"),
+    ).toHaveValue("09/20/2023");
+  });
+
+  it("should populate the `Relative dates…` date picker with the value passed in", () => {
+    setup({ value: "past30days" });
+    expect(screen.getByTestId("relative-datetime-value")).toHaveValue("30");
+    expect(screen.getByTestId("select-button-content")).toHaveTextContent(
+      "days",
+    );
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchFilterDatePicker/index.ts b/frontend/src/metabase/search/components/SearchFilterDatePicker/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..380f197ca65d4adebc838f28194b0813ade82705
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterDatePicker/index.ts
@@ -0,0 +1 @@
+export * from "./SearchFilterDatePicker";
diff --git a/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5107af5a623cbe0903086b7bf4f1812efa5d9d45
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.styled.tsx
@@ -0,0 +1,19 @@
+import styled from "@emotion/styled";
+import { css } from "@emotion/react";
+import { Stack } from "metabase/ui";
+
+export const SearchPopoverContainer = styled(Stack)`
+  overflow: hidden;
+  width: 100%;
+`;
+export const DropdownApplyButtonDivider = styled.hr<{ width?: string }>`
+  border-width: 1px 0 0 0;
+  border-style: solid;
+  ${({ theme, width }) => {
+    const dividerWidth = width ?? "100%";
+    return css`
+      border-color: ${theme.colors.border[0]};
+      width: ${dividerWidth};
+    `;
+  }}
+`;
diff --git a/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.tsx b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5b4ff30d6f202cf7aa09a1813218faa130461dce
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.tsx
@@ -0,0 +1,51 @@
+import { t } from "ttag";
+import type { ReactNode } from "react";
+import type { StackProps } from "metabase/ui";
+import { Button, Center, Group, Loader, FocusTrap } from "metabase/ui";
+import type {
+  FilterTypeKeys,
+  SearchFilterPropTypes,
+} from "metabase/search/types";
+import {
+  DropdownApplyButtonDivider,
+  SearchPopoverContainer,
+} from "./SearchFilterPopoverWrapper.styled";
+
+type SearchFilterPopoverWrapperProps<T extends FilterTypeKeys = any> = {
+  children: ReactNode;
+  onApply: (value: SearchFilterPropTypes[T]) => void;
+  isLoading?: boolean;
+} & StackProps;
+
+export const SearchFilterApplyButton = ({
+  onApply,
+}: Pick<SearchFilterPopoverWrapperProps, "onApply">) => (
+  <Button variant="filled" onClick={onApply}>{t`Apply`}</Button>
+);
+
+export const SearchFilterPopoverWrapper = ({
+  children,
+  onApply,
+  isLoading = false,
+  ...stackProps
+}: SearchFilterPopoverWrapperProps) => {
+  if (isLoading) {
+    return (
+      <Center p="lg">
+        <Loader data-testid="loading-spinner" />
+      </Center>
+    );
+  }
+
+  return (
+    <FocusTrap active>
+      <SearchPopoverContainer spacing={0} {...stackProps}>
+        {children}
+        <DropdownApplyButtonDivider />
+        <Group position="right" align="center" px="sm" pb="sm">
+          <SearchFilterApplyButton onApply={onApply} />
+        </Group>
+      </SearchPopoverContainer>
+    </FocusTrap>
+  );
+};
diff --git a/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.unit.spec.tsx b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c7dd12d38c8f35fa5cbb9c1107efa1f16637dd6d
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/SearchFilterPopoverWrapper.unit.spec.tsx
@@ -0,0 +1,46 @@
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders, screen } from "__support__/ui";
+import { SearchFilterPopoverWrapper } from "./SearchFilterPopoverWrapper";
+
+type SetupProps = {
+  isLoading?: boolean;
+  onApply?: jest.Func;
+};
+
+const setup = ({ isLoading = false }: SetupProps = {}) => {
+  const onApply = jest.fn();
+
+  renderWithProviders(
+    <SearchFilterPopoverWrapper isLoading={isLoading} onApply={onApply}>
+      Children Content
+    </SearchFilterPopoverWrapper>,
+  );
+
+  return {
+    onApply,
+  };
+};
+describe("SearchFilterPopoverWrapper", () => {
+  it("should render loading spinner when isLoading is true", () => {
+    setup({ isLoading: true });
+
+    const loadingSpinner = screen.getByTestId("loading-spinner");
+    expect(loadingSpinner).toBeInTheDocument();
+  });
+
+  it("should render children content when isLoading is false", () => {
+    setup();
+
+    const childrenContent = screen.getByText("Children Content");
+    expect(childrenContent).toBeInTheDocument();
+  });
+
+  it('should call onApply when the "Apply" button is clicked', () => {
+    const { onApply } = setup();
+
+    const applyButton = screen.getByText("Apply");
+    userEvent.click(applyButton);
+
+    expect(onApply).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/index.ts b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a7ca94eb0341baf98c5ba58b20f53583c87e6b6
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchFilterPopoverWrapper/index.ts
@@ -0,0 +1 @@
+export { SearchFilterPopoverWrapper } from "./SearchFilterPopoverWrapper";
diff --git a/frontend/src/metabase/search/components/SearchResult/SearchResult.styled.tsx b/frontend/src/metabase/search/components/SearchResult/SearchResult.styled.tsx
index a113aecdc30ebb88f2ac7530580013d42beb1e17..48d392b70d9b8063152c56391f606800a487e247 100644
--- a/frontend/src/metabase/search/components/SearchResult/SearchResult.styled.tsx
+++ b/frontend/src/metabase/search/components/SearchResult/SearchResult.styled.tsx
@@ -1,118 +1,101 @@
+import isPropValid from "@emotion/is-prop-valid";
+import { css } from "@emotion/react";
 import styled from "@emotion/styled";
-
-import { color, lighten } from "metabase/lib/colors";
-import { space } from "metabase/styled-components/theme";
-import Link from "metabase/core/components/Link";
-import Text from "metabase/components/type/Text";
-import LoadingSpinner from "metabase/components/LoadingSpinner";
-
-interface ResultStylesProps {
-  compact: boolean;
-  active: boolean;
-  isSelected: boolean;
-}
-
-export const TitleWrapper = styled.div`
-  display: flex;
-  grid-gap: 0.25rem;
-  align-items: center;
-`;
-
-export const Title = styled("h3")<{ active: boolean }>`
-  margin-bottom: 4px;
-  color: ${props => color(props.active ? "text-dark" : "text-medium")};
-`;
-
-export const ResultButton = styled.button<ResultStylesProps>`
-  ${props => resultStyles(props)}
-  padding-right: 0.5rem;
-  text-align: left;
-  cursor: pointer;
-  width: 100%;
-
-  &:hover {
-    ${Title} {
-      color: ${color("brand")};
-    }
+import type { AnchorHTMLAttributes, HTMLAttributes, RefObject } from "react";
+import { PLUGIN_MODERATION } from "metabase/plugins";
+import type { AnchorProps, BoxProps } from "metabase/ui";
+import { Box, Divider, Stack, Anchor } from "metabase/ui";
+
+const { ModerationStatusIcon } = PLUGIN_MODERATION;
+
+const isBoxPropValid = (propName: PropertyKey) => {
+  return (
+    propName !== "isActive" &&
+    propName !== "isSelected" &&
+    isPropValid(propName)
+  );
+};
+
+export const ResultTitle = styled(Anchor)<
+  AnchorProps & AnchorHTMLAttributes<HTMLAnchorElement>
+>`
+  line-height: unset;
+  font-weight: 700;
+  font-size: ${({ theme }) => theme.fontSizes.md};
+
+  color: ${({ theme }) => theme.colors.text[2]};
+
+  &:hover,
+  &:focus-visible,
+  &:focus {
+    text-decoration: none;
+    color: ${({ theme }) => theme.colors.brand[1]};
+    outline: 0;
   }
 `;
 
-export const ResultLink = styled(Link)<ResultStylesProps>`
-  ${props => resultStyles(props)}
-`;
-
-const resultStyles = ({ compact, active, isSelected }: ResultStylesProps) => `
-  display: block;
-  background-color: ${isSelected ? lighten("brand", 0.63) : "transparent"};
-  min-height: ${compact ? "36px" : "54px"};
-  padding-top: ${space(1)};
-  padding-bottom: ${space(1)};
-  padding-left: 14px;
-  padding-right: ${compact ? "20px" : space(3)};
-  cursor: ${active ? "pointer" : "default"};
-
-  &:hover {
-    background-color: ${active ? lighten("brand", 0.63) : ""};
-
-    h3 {
-      color: ${active || isSelected ? color("brand") : ""};
+export const SearchResultContainer = styled(Box, {
+  shouldForwardProp: isBoxPropValid,
+})<
+  BoxProps &
+    HTMLAttributes<HTMLButtonElement> & {
+      isActive?: boolean;
+      isSelected?: boolean;
+      component?: string;
+      ref?: RefObject<HTMLButtonElement> | null;
     }
-  }
-
-  ${Link.Root} {
-    text-underline-position: under;
-    text-decoration: underline ${color("text-light")};
-    text-decoration-style: dashed;
-
-    &:hover {
-      color: ${active ? color("brand") : ""};
-      text-decoration-color: ${active ? color("brand") : ""};
-    }
-  }
-
-  ${Text} {
-    margin-top: 0;
-    margin-bottom: 0;
-    font-size: 13px;
-    line-height: 19px;
-  }
-
-  h3 {
-    font-size: ${compact ? "14px" : "16px"};
-    line-height: 1.2em;
-    overflow-wrap: anywhere;
-    margin-bottom: 0;
-    color: ${active && isSelected ? color("brand") : ""};
-  }
+>`
+  display: grid;
+  grid-template-columns: auto 1fr auto;
+  justify-content: center;
+  align-items: center;
+  gap: 0.5rem 0.75rem;
+
+  padding: ${({ theme }) => theme.spacing.sm};
+
+  ${({ theme, isActive, isSelected }) =>
+    isActive &&
+    css`
+      border-radius: ${theme.radius.md};
+      color: ${isSelected && theme.colors.brand[1]};
+      background-color: ${isSelected && theme.colors.brand[0]};
+
+      ${ResultTitle} {
+        color: ${isSelected && theme.colors.brand[1]};
+      }
+
+      &:hover {
+        background-color: ${theme.colors.brand[0]};
+        cursor: pointer;
+
+        ${ResultTitle} {
+          color: ${theme.colors.brand[1]};
+        }
+      }
+
+      &:focus-within {
+        background-color: ${theme.colors.brand[0]};
+      }
+    `}
+`;
 
-  .Icon-info {
-    color: ${color("text-light")};
-  }
+export const ResultNameSection = styled(Stack)`
+  overflow: hidden;
 `;
 
-export const ResultInner = styled.div`
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
+export const ModerationIcon = styled(ModerationStatusIcon)`
+  overflow: unset;
 `;
 
-export const ResultLinkContent = styled.div`
-  display: flex;
-  align-items: start;
-  overflow-wrap: anywhere;
+export const LoadingSection = styled(Box)<BoxProps>`
+  grid-row: 1 / span 2;
+  grid-column: 3;
 `;
 
-export const Description = styled(Text)`
-  padding-left: ${space(1)};
-  margin-top: ${space(1)} !important;
-  border-left: 2px solid ${lighten("brand", 0.45)};
+export const DescriptionSection = styled(Box)`
+  grid-column-start: 2;
 `;
 
-export const ResultSpinner = styled(LoadingSpinner)`
-  display: flex;
-  flex-grow: 1;
-  align-self: center;
-  justify-content: flex-end;
-  margin-left: ${space(1)};
-  color: ${color("brand")};
+export const DescriptionDivider = styled(Divider)`
+  border-radius: ${({ theme }) => theme.radius.xs};
 `;
diff --git a/frontend/src/metabase/search/components/SearchResult/SearchResult.tsx b/frontend/src/metabase/search/components/SearchResult/SearchResult.tsx
index 2cefec7147beb375854962044f008c953c819551..f6ff0f81bceddf2067bb2a698da170ccc252eb29 100644
--- a/frontend/src/metabase/search/components/SearchResult/SearchResult.tsx
+++ b/frontend/src/metabase/search/components/SearchResult/SearchResult.tsx
@@ -1,126 +1,141 @@
-import { Button } from "metabase/ui";
-import { isSyncCompleted } from "metabase/lib/syncing";
-import { Icon } from "metabase/core/components/Icon";
-import Text from "metabase/components/type/Text";
+import type { LocationDescriptorObject } from "history";
+import type { MouseEvent } from "react";
+import { useCallback } from "react";
+import { push } from "react-router-redux";
 
-import { PLUGIN_MODERATION } from "metabase/plugins";
+import { useDispatch } from "metabase/lib/redux";
+import { Group, Text, Loader } from "metabase/ui";
+import { isSyncCompleted } from "metabase/lib/syncing";
 
 import type { WrappedResult } from "metabase/search/types";
-import Link from "metabase/core/components/Link";
 import { InfoText } from "../InfoText";
-import { ItemIcon, Context, Score } from "./components";
+import { ItemIcon } from "./components";
+
 import {
-  ResultButton,
-  ResultLink,
-  Title,
-  TitleWrapper,
-  Description,
-  ResultSpinner,
-  ResultLinkContent,
-  ResultInner,
+  DescriptionDivider,
+  DescriptionSection,
+  LoadingSection,
+  ModerationIcon,
+  ResultNameSection,
+  ResultTitle,
+  SearchResultContainer,
 } from "./SearchResult.styled";
 
 export function SearchResult({
   result,
   compact = false,
-  hasDescription = true,
-  onClick = undefined,
+  showDescription = true,
   isSelected = false,
+  onClick,
 }: {
   result: WrappedResult;
   compact?: boolean;
-  hasDescription?: boolean;
+  showDescription?: boolean;
   onClick?: (result: WrappedResult) => void;
   isSelected?: boolean;
 }) {
-  const active = isItemActive(result);
-  const loading = isItemLoading(result);
+  const { name, model, description, moderated_status }: WrappedResult = result;
+
+  const isActive = isItemActive(result);
+  const isLoading = isItemLoading(result);
+
+  const dispatch = useDispatch();
 
-  // we want to remove link behavior if we have an onClick handler
-  const ResultContainer = onClick ? ResultButton : ResultLink;
+  const onChangeLocation = useCallback(
+    (nextLocation: LocationDescriptorObject | string) =>
+      dispatch(push(nextLocation)),
+    [dispatch],
+  );
+
+  const handleClick = (e: MouseEvent) => {
+    e.stopPropagation();
+    e.preventDefault();
+
+    if (!isActive) {
+      return;
+    }
 
-  const showXRayButton =
-    result.model === "indexed-entity" &&
-    result.id !== undefined &&
-    result.model_index_id !== null;
+    if (onClick) {
+      onClick(result);
+      return;
+    }
+
+    onChangeLocation(result.getUrl());
+  };
 
   return (
-    <ResultContainer
-      data-is-selected={isSelected}
-      isSelected={isSelected}
-      active={active}
-      compact={compact}
-      to={!onClick ? result.getUrl() : ""}
-      onClick={onClick && active ? () => onClick(result) : undefined}
+    <SearchResultContainer
       data-testid="search-result-item"
+      component="button"
+      onClick={handleClick}
+      isActive={isActive}
+      isSelected={isSelected}
+      data-model-type={model}
+      data-is-selected={isSelected}
+      w="100%"
+      aria-label={`${name} ${model}`}
     >
-      <ResultInner>
-        <ResultLinkContent>
-          <ItemIcon item={result} type={result.model} active={active} />
-          <div>
-            <TitleWrapper>
-              <Title active={active} data-testid="search-result-item-name">
-                {result.name}
-              </Title>
-              <PLUGIN_MODERATION.ModerationStatusIcon
-                status={result.moderated_status}
-                size={12}
-              />
-            </TitleWrapper>
-            <Text data-testid="result-link-info-text">
-              <InfoText result={result} />
-            </Text>
-            {hasDescription && result.description && (
-              <Description>{result.description}</Description>
-            )}
-            <Score scores={result.scores} />
-          </div>
-          {loading && (
-            // SearchApp also uses `loading-spinner`, using a different test ID
-            // to not confuse unit tests waiting for loading-spinner to disappear
-            <ResultSpinner
-              data-testid="search-result-loading-spinner"
-              size={24}
-              borderWidth={3}
-            />
-          )}
-        </ResultLinkContent>
-        {showXRayButton && (
-          <Button
-            onClick={(e: React.MouseEvent<HTMLButtonElement>) =>
-              e.stopPropagation()
-            }
-            variant="outline"
-            p="sm"
+      <ItemIcon
+        data-testid="search-result-item-icon"
+        active={isActive}
+        item={result}
+        type={model}
+      />
+      <ResultNameSection justify="center" spacing="xs">
+        <Group spacing="xs" align="center" noWrap>
+          <ResultTitle
+            role="heading"
+            data-testid="search-result-item-name"
+            truncate
+            href={!onClick ? result.getUrl() : undefined}
           >
-            <Link
-              to={`/auto/dashboard/model_index/${result.model_index_id}/primary_key/${result.id}`}
+            {name}
+          </ResultTitle>
+          <ModerationIcon status={moderated_status} filled size={14} />
+        </Group>
+        <InfoText result={result} isCompact={compact} />
+      </ResultNameSection>
+      {isLoading && (
+        <LoadingSection px="xs">
+          <Loader />
+        </LoadingSection>
+      )}
+      {description && showDescription && (
+        <DescriptionSection>
+          <Group noWrap spacing="sm">
+            <DescriptionDivider
+              size="md"
+              color="focus.0"
+              orientation="vertical"
+            />
+            <Text
+              data-testid="result-description"
+              color="text.1"
+              align="left"
+              size="sm"
+              lineClamp={2}
             >
-              <Icon name="bolt" />
-            </Link>
-          </Button>
-        )}
-      </ResultInner>
-      {compact || <Context context={result.context} />}
-    </ResultContainer>
+              {description}
+            </Text>
+          </Group>
+        </DescriptionSection>
+      )}
+    </SearchResultContainer>
   );
 }
 
 const isItemActive = (result: WrappedResult) => {
-  switch (result.model) {
-    case "table":
-      return isSyncCompleted(result);
-    default:
-      return true;
+  if (result.model !== "table") {
+    return true;
   }
+
+  return isSyncCompleted(result);
 };
 
 const isItemLoading = (result: WrappedResult) => {
-  switch (result.model) {
-    case "database":
-    case "table":
-      return !isSyncCompleted(result);
-    default:
-      return false;
+  if (result.model !== "database" && result.model !== "table") {
+    return false;
   }
+
+  return !isSyncCompleted(result);
 };
diff --git a/frontend/src/metabase/search/components/SearchResult/SearchResult.unit.spec.tsx b/frontend/src/metabase/search/components/SearchResult/SearchResult.unit.spec.tsx
deleted file mode 100644
index 214564b3ffb8cb3b2dbc61bef1c3f775743dd547..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/search/components/SearchResult/SearchResult.unit.spec.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import userEvent from "@testing-library/user-event";
-import { setupEnterpriseTest } from "__support__/enterprise";
-import {
-  setupTableEndpoints,
-  setupDatabaseEndpoints,
-} from "__support__/server-mocks";
-import {
-  createMockSearchResult,
-  createMockTable,
-  createMockDatabase,
-} from "metabase-types/api/mocks";
-import {
-  getIcon,
-  renderWithProviders,
-  queryIcon,
-  screen,
-} from "__support__/ui";
-
-import type { InitialSyncStatus } from "metabase-types/api";
-import type { WrappedResult } from "metabase/search/types";
-import { SearchResult } from "./SearchResult";
-
-const createWrappedSearchResult = (
-  options: Partial<WrappedResult>,
-): WrappedResult => {
-  const result = createMockSearchResult(options);
-
-  return {
-    ...result,
-    getUrl: options.getUrl ?? (() => "/collection/root"),
-    getIcon: options.getIcon ?? (() => ({ name: "folder" })),
-    getCollection: options.getCollection ?? (() => result.collection),
-  };
-};
-
-describe("SearchResult", () => {
-  it("renders a search result question item", () => {
-    const result = createWrappedSearchResult({
-      name: "My Item",
-      model: "card",
-      description: "My Item Description",
-      getIcon: () => ({ name: "table" }),
-    });
-
-    renderWithProviders(<SearchResult result={result} />);
-
-    expect(screen.getByText(result.name)).toBeInTheDocument();
-    expect(screen.getByText(result.description as string)).toBeInTheDocument();
-    expect(getIcon("table")).toBeInTheDocument();
-  });
-
-  it("renders a search result collection item", () => {
-    const result = createWrappedSearchResult({
-      name: "My Folder of Goodies",
-      model: "collection",
-      collection: {
-        id: 1,
-        name: "This should not appear",
-        authority_level: null,
-      },
-    });
-
-    renderWithProviders(<SearchResult result={result} />);
-
-    expect(screen.getByText(result.name)).toBeInTheDocument();
-    expect(screen.getByText("Collection")).toBeInTheDocument();
-    expect(screen.queryByText(result.collection.name)).not.toBeInTheDocument();
-    expect(getIcon("folder")).toBeInTheDocument();
-  });
-});
-
-describe("SearchResult > Tables", () => {
-  interface SetupOpts {
-    name: string;
-    initial_sync_status: InitialSyncStatus;
-  }
-
-  const setup = (setupOpts: SetupOpts) => {
-    const TEST_TABLE = createMockTable(setupOpts);
-    const TEST_DATABASE = createMockDatabase();
-    setupTableEndpoints(TEST_TABLE);
-    setupDatabaseEndpoints(TEST_DATABASE);
-    const result = createWrappedSearchResult({
-      model: "table",
-      table_id: TEST_TABLE.id,
-      database_id: TEST_DATABASE.id,
-      getUrl: () => `/table/${TEST_TABLE.id}`,
-      getIcon: () => ({ name: "table" }),
-      ...setupOpts,
-    });
-    const onClick = jest.fn();
-    renderWithProviders(<SearchResult result={result} onClick={onClick} />);
-    const link = screen.getByText(result.name);
-    return { link, onClick };
-  };
-
-  it("tables with initial_sync_status='complete' are clickable", () => {
-    const { link, onClick } = setup({
-      name: "Complete Table",
-      initial_sync_status: "complete",
-    });
-    userEvent.click(link);
-    expect(onClick).toHaveBeenCalled();
-  });
-
-  it("tables with initial_sync_status='incomplete' are not clickable", () => {
-    const { link, onClick } = setup({
-      name: "Incomplete Table",
-      initial_sync_status: "incomplete",
-    });
-    userEvent.click(link);
-    expect(onClick).not.toHaveBeenCalled();
-  });
-
-  it("tables with initial_sync_status='aborted' are not clickable", () => {
-    const { link, onClick } = setup({
-      name: "Aborted Table",
-      initial_sync_status: "aborted",
-    });
-    userEvent.click(link);
-    expect(onClick).not.toHaveBeenCalled();
-  });
-});
-
-describe("SearchResult > Collections", () => {
-  const resultInRegularCollection = createWrappedSearchResult({
-    name: "My Regular Item",
-    collection_authority_level: null,
-    collection: {
-      id: 1,
-      name: "Regular Collection",
-      authority_level: null,
-    },
-  });
-
-  const resultInOfficalCollection = createWrappedSearchResult({
-    name: "My Official Item",
-    collection_authority_level: "official",
-    collection: {
-      id: 1,
-      name: "Official Collection",
-      authority_level: "official",
-    },
-  });
-
-  describe("OSS", () => {
-    it("renders regular collection correctly", () => {
-      renderWithProviders(<SearchResult result={resultInRegularCollection} />);
-      expect(
-        screen.getByText(resultInRegularCollection.name),
-      ).toBeInTheDocument();
-      expect(screen.getByText("Regular Collection")).toBeInTheDocument();
-      expect(getIcon("folder")).toBeInTheDocument();
-      expect(queryIcon("badge")).not.toBeInTheDocument();
-    });
-
-    it("renders official collections as regular", () => {
-      renderWithProviders(<SearchResult result={resultInOfficalCollection} />);
-      expect(
-        screen.getByText(resultInOfficalCollection.name),
-      ).toBeInTheDocument();
-      expect(screen.getByText("Official Collection")).toBeInTheDocument();
-      expect(getIcon("folder")).toBeInTheDocument();
-      expect(queryIcon("badge")).not.toBeInTheDocument();
-    });
-  });
-
-  describe("EE", () => {
-    const resultInOfficalCollectionEE: WrappedResult = {
-      ...resultInOfficalCollection,
-      getIcon: () => ({ name: "badge" }),
-    };
-
-    beforeAll(() => {
-      setupEnterpriseTest();
-    });
-
-    it("renders regular collection correctly", () => {
-      renderWithProviders(<SearchResult result={resultInRegularCollection} />);
-      expect(
-        screen.getByText(resultInRegularCollection.name),
-      ).toBeInTheDocument();
-      expect(screen.getByText("Regular Collection")).toBeInTheDocument();
-      expect(getIcon("folder")).toBeInTheDocument();
-      expect(queryIcon("badge")).not.toBeInTheDocument();
-    });
-
-    it("renders official collections correctly", () => {
-      renderWithProviders(
-        <SearchResult result={resultInOfficalCollectionEE} />,
-      );
-      expect(
-        screen.getByText(resultInOfficalCollectionEE.name),
-      ).toBeInTheDocument();
-      expect(screen.getByText("Official Collection")).toBeInTheDocument();
-      expect(getIcon("badge")).toBeInTheDocument();
-      expect(queryIcon("folder")).not.toBeInTheDocument();
-    });
-  });
-});
diff --git a/frontend/src/metabase/search/components/SearchResult/components/CollectionIcon.tsx b/frontend/src/metabase/search/components/SearchResult/components/CollectionIcon.tsx
index 718ac1d18c5b312ba4e3811a1e1dec5bcb39a4dc..82ba19557793ad6836f4a0b73dfd9391b3d9a659 100644
--- a/frontend/src/metabase/search/components/SearchResult/components/CollectionIcon.tsx
+++ b/frontend/src/metabase/search/components/SearchResult/components/CollectionIcon.tsx
@@ -1,14 +1,23 @@
 import { Icon } from "metabase/core/components/Icon";
+import { color } from "metabase/lib/colors";
 import { PLUGIN_COLLECTIONS } from "metabase/plugins";
 import { DEFAULT_ICON_SIZE } from "metabase/search/components/SearchResult/components";
-import type { WrappedResult } from "metabase/search/types";
+import type { IconComponentProps } from "./ItemIcon";
 
-export function CollectionIcon({ item }: { item: WrappedResult }) {
+export function CollectionIcon({ item }: { item: IconComponentProps["item"] }) {
   const iconProps = { ...item.getIcon(), tooltip: null };
-  const isRegular = PLUGIN_COLLECTIONS.isRegularCollection(item.collection);
+  const isRegular =
+    "collection" in item &&
+    PLUGIN_COLLECTIONS.isRegularCollection(item.collection);
 
   if (isRegular) {
-    return <Icon {...iconProps} size={DEFAULT_ICON_SIZE} />;
+    return (
+      <Icon
+        {...iconProps}
+        size={DEFAULT_ICON_SIZE}
+        color={color("text-light")}
+      />
+    );
   }
 
   return <Icon {...iconProps} width={20} height={24} />;
diff --git a/frontend/src/metabase/search/components/SearchResult/components/DefaultIcon.tsx b/frontend/src/metabase/search/components/SearchResult/components/DefaultIcon.tsx
index d807debe44a61f76031f43261abb48c08acf6dff..ab25df33948326db6ab42c083d7442f1e29029b1 100644
--- a/frontend/src/metabase/search/components/SearchResult/components/DefaultIcon.tsx
+++ b/frontend/src/metabase/search/components/SearchResult/components/DefaultIcon.tsx
@@ -1,7 +1,7 @@
 import { Icon } from "metabase/core/components/Icon";
-import type { WrappedResult } from "metabase/search/types";
+import type { IconComponentProps } from "./ItemIcon";
 import { DEFAULT_ICON_SIZE } from "./constants";
 
-export function DefaultIcon({ item }: { item: WrappedResult }) {
+export function DefaultIcon({ item }: { item: IconComponentProps["item"] }) {
   return <Icon {...item.getIcon()} size={DEFAULT_ICON_SIZE} />;
 }
diff --git a/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.styled.tsx b/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.styled.tsx
index 5199373ce15755cd7718e20c5d991ae7a45457c6..b030b695eb5651dac71c2d2e252d99609f865acc 100644
--- a/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.styled.tsx
+++ b/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.styled.tsx
@@ -22,12 +22,14 @@ export const IconWrapper = styled.div<{
   active: boolean;
   type: SearchModelType;
 }>`
+  border: ${({ theme }) => `1px solid ${theme.colors.border[0]}`};
+  border-radius: ${({ theme }) => theme.radius.sm};
   display: flex;
   align-items: center;
   justify-content: center;
   width: 32px;
   height: 32px;
   color: ${({ active, type }) => getColorForIconWrapper({ active, type })};
-  margin-right: 10px;
   flex-shrink: 0;
+  background: ${color("white")};
 `;
diff --git a/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.tsx b/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.tsx
index 9aa6cccc7d87ed591ccfe42421d72d7eaed2c683..b8cec6448959a85af8f5746f9ebc78dcf543fe74 100644
--- a/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.tsx
+++ b/frontend/src/metabase/search/components/SearchResult/components/ItemIcon.tsx
@@ -1,12 +1,13 @@
 import type { SearchModelType } from "metabase-types/api";
 import { Icon } from "metabase/core/components/Icon";
 import type { WrappedResult } from "metabase/search/types";
+import type { WrappedRecentItem } from "metabase/nav/components/search/RecentsList";
 import { CollectionIcon } from "./CollectionIcon";
 import { DefaultIcon } from "./DefaultIcon";
 import { IconWrapper } from "./ItemIcon.styled";
 
-interface IconComponentProps {
-  item: WrappedResult;
+export interface IconComponentProps {
+  item: WrappedResult | WrappedRecentItem;
   type: SearchModelType;
 }
 
@@ -24,13 +25,19 @@ const IconComponent = ({ item, type }: IconComponentProps) => {
 
 interface ItemIconProps {
   active: boolean;
-  item: WrappedResult;
+  item: WrappedResult | WrappedRecentItem;
   type: SearchModelType;
+  "data-testid"?: string;
 }
 
-export const ItemIcon = ({ active, item, type }: ItemIconProps) => {
+export const ItemIcon = ({
+  active,
+  item,
+  type,
+  "data-testid": dataTestId,
+}: ItemIconProps) => {
   return (
-    <IconWrapper type={type} active={active}>
+    <IconWrapper type={type} active={active} data-testid={dataTestId}>
       <IconComponent item={item} type={type} />
     </IconWrapper>
   );
diff --git a/frontend/src/metabase/search/components/SearchResult/index.ts b/frontend/src/metabase/search/components/SearchResult/index.ts
index 09ed6feab213d7d2dca789828666878a5a6fd791..330f1efd5dba9797556483471282d416032f03e3 100644
--- a/frontend/src/metabase/search/components/SearchResult/index.ts
+++ b/frontend/src/metabase/search/components/SearchResult/index.ts
@@ -1,9 +1,3 @@
 export { SearchResult } from "./SearchResult";
-export {
-  ResultLink,
-  ResultButton,
-  ResultSpinner,
-  Title,
-  TitleWrapper,
-} from "./SearchResult.styled";
 export * from "./components";
+export * from "./SearchResult.styled";
diff --git a/frontend/src/metabase/search/components/SearchResult/tests/SearchResult-Collections.unit.spec.tsx b/frontend/src/metabase/search/components/SearchResult/tests/SearchResult-Collections.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e67365fad80e39a444248e215183fc44707bed01
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResult/tests/SearchResult-Collections.unit.spec.tsx
@@ -0,0 +1,129 @@
+import { waitFor } from "@testing-library/react";
+import { setupEnterpriseTest } from "__support__/enterprise";
+import {
+  setupCollectionByIdEndpoint,
+  setupUsersEndpoints,
+} from "__support__/server-mocks";
+import {
+  getIcon,
+  queryIcon,
+  renderWithProviders,
+  screen,
+} from "__support__/ui";
+import { createMockCollection, createMockUser } from "metabase-types/api/mocks";
+import { SearchResult } from "metabase/search/components/SearchResult";
+import type { WrappedResult } from "metabase/search/types";
+import { createWrappedSearchResult } from "./util";
+
+const TEST_REGULAR_COLLECTION = createMockCollection({
+  id: 1,
+  name: "Regular Collection",
+  authority_level: null,
+});
+
+const TEST_OFFICIAL_COLLECTION = createMockCollection({
+  id: 2,
+  name: "Official Collection",
+  authority_level: "official",
+});
+
+const resultInRegularCollection = createWrappedSearchResult({
+  name: "My Regular Item",
+  collection_authority_level: null,
+  collection: TEST_REGULAR_COLLECTION,
+});
+
+const resultInOfficalCollection = createWrappedSearchResult({
+  name: "My Official Item",
+  collection_authority_level: "official",
+  collection: TEST_OFFICIAL_COLLECTION,
+});
+
+const setup = ({ result }: { result: WrappedResult }) => {
+  setupCollectionByIdEndpoint({
+    collections: [TEST_REGULAR_COLLECTION, TEST_OFFICIAL_COLLECTION],
+  });
+
+  setupUsersEndpoints([createMockUser()]);
+
+  renderWithProviders(<SearchResult result={result} />);
+};
+
+describe("SearchResult > Collections", () => {
+  describe("OSS", () => {
+    it("renders regular collection correctly", async () => {
+      setup({ result: resultInRegularCollection });
+      expect(
+        screen.getByText(resultInRegularCollection.name),
+      ).toBeInTheDocument();
+      await waitFor(() => {
+        expect(
+          screen.queryByTestId("info-text-collection-loading-text"),
+        ).not.toBeInTheDocument();
+      });
+      expect(screen.getByText("Regular Collection")).toBeInTheDocument();
+      expect(getIcon("folder")).toBeInTheDocument();
+      expect(queryIcon("badge")).not.toBeInTheDocument();
+    });
+
+    it("renders official collections as regular", async () => {
+      setup({ result: resultInOfficalCollection });
+      expect(
+        screen.getByText(resultInOfficalCollection.name),
+      ).toBeInTheDocument();
+      await waitFor(() => {
+        expect(
+          screen.queryByTestId("info-text-collection-loading-text"),
+        ).not.toBeInTheDocument();
+      });
+      expect(screen.getByText("Official Collection")).toBeInTheDocument();
+      expect(getIcon("folder")).toBeInTheDocument();
+      expect(queryIcon("badge")).not.toBeInTheDocument();
+    });
+  });
+
+  describe("EE", () => {
+    const resultInOfficalCollectionEE: WrappedResult = {
+      ...resultInOfficalCollection,
+      getIcon: () => ({ name: "badge" }),
+    };
+
+    beforeAll(() => {
+      setupEnterpriseTest();
+    });
+
+    it("renders regular collection correctly", async () => {
+      setup({ result: resultInRegularCollection });
+      expect(
+        screen.getByText(resultInRegularCollection.name),
+      ).toBeInTheDocument();
+
+      await waitFor(() => {
+        expect(
+          screen.queryByTestId("info-text-collection-loading-text"),
+        ).not.toBeInTheDocument();
+      });
+
+      expect(screen.getByText("Regular Collection")).toBeInTheDocument();
+      expect(getIcon("folder")).toBeInTheDocument();
+      expect(queryIcon("badge")).not.toBeInTheDocument();
+    });
+
+    it("renders official collections correctly", async () => {
+      setup({ result: resultInOfficalCollectionEE });
+      expect(
+        screen.getByText(resultInOfficalCollectionEE.name),
+      ).toBeInTheDocument();
+
+      await waitFor(() => {
+        expect(
+          screen.queryByTestId("info-text-collection-loading-text"),
+        ).not.toBeInTheDocument();
+      });
+
+      expect(screen.getByText("Official Collection")).toBeInTheDocument();
+      expect(getIcon("badge")).toBeInTheDocument();
+      expect(queryIcon("folder")).not.toBeInTheDocument();
+    });
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchResult/tests/SearchResult-Tables.unit.spec.tsx b/frontend/src/metabase/search/components/SearchResult/tests/SearchResult-Tables.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8464a06cb2704f42917b7af5c81df65d1fc47df2
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResult/tests/SearchResult-Tables.unit.spec.tsx
@@ -0,0 +1,70 @@
+import userEvent from "@testing-library/user-event";
+import {
+  setupDatabaseEndpoints,
+  setupTableEndpoints,
+  setupUsersEndpoints,
+} from "__support__/server-mocks";
+import { renderWithProviders, screen } from "__support__/ui";
+import type { InitialSyncStatus } from "metabase-types/api";
+import {
+  createMockDatabase,
+  createMockTable,
+  createMockUser,
+} from "metabase-types/api/mocks";
+import { SearchResult } from "metabase/search/components/SearchResult";
+import { createWrappedSearchResult } from "metabase/search/components/SearchResult/tests/util";
+
+interface SetupOpts {
+  name: string;
+  initial_sync_status: InitialSyncStatus;
+}
+
+const setup = (setupOpts: SetupOpts) => {
+  const TEST_TABLE = createMockTable(setupOpts);
+  const TEST_DATABASE = createMockDatabase();
+  setupTableEndpoints(TEST_TABLE);
+  setupDatabaseEndpoints(TEST_DATABASE);
+  setupUsersEndpoints([createMockUser()]);
+  const result = createWrappedSearchResult({
+    model: "table",
+    table_id: TEST_TABLE.id,
+    database_id: TEST_DATABASE.id,
+    getUrl: () => `/table/${TEST_TABLE.id}`,
+    getIcon: () => ({ name: "table" }),
+    ...setupOpts,
+  });
+
+  const onClick = jest.fn();
+  renderWithProviders(<SearchResult result={result} onClick={onClick} />);
+  const link = screen.getByText(result.name);
+  return { link, onClick };
+};
+
+describe("SearchResult > Tables", () => {
+  it("tables with initial_sync_status='complete' are clickable", () => {
+    const { link, onClick } = setup({
+      name: "Complete Table",
+      initial_sync_status: "complete",
+    });
+    userEvent.click(link);
+    expect(onClick).toHaveBeenCalled();
+  });
+
+  it("tables with initial_sync_status='incomplete' are not clickable", () => {
+    const { link, onClick } = setup({
+      name: "Incomplete Table",
+      initial_sync_status: "incomplete",
+    });
+    userEvent.click(link);
+    expect(onClick).not.toHaveBeenCalled();
+  });
+
+  it("tables with initial_sync_status='aborted' are not clickable", () => {
+    const { link, onClick } = setup({
+      name: "Aborted Table",
+      initial_sync_status: "aborted",
+    });
+    userEvent.click(link);
+    expect(onClick).not.toHaveBeenCalled();
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchResult/tests/SearchResult.unit.spec.tsx b/frontend/src/metabase/search/components/SearchResult/tests/SearchResult.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4b80e466c97d92420f0e104b01a04e717ea2c444
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResult/tests/SearchResult.unit.spec.tsx
@@ -0,0 +1,61 @@
+import {
+  setupCollectionByIdEndpoint,
+  setupUsersEndpoints,
+} from "__support__/server-mocks";
+import { getIcon, renderWithProviders, screen } from "__support__/ui";
+import { createMockCollection, createMockUser } from "metabase-types/api/mocks";
+import { SearchResult } from "metabase/search/components/SearchResult/SearchResult";
+import { createWrappedSearchResult } from "metabase/search/components/SearchResult/tests/util";
+import type { WrappedResult } from "metabase/search/types";
+
+const TEST_REGULAR_COLLECTION = createMockCollection({
+  id: 1,
+  name: "Regular Collection",
+  authority_level: null,
+});
+
+const TEST_RESULT_QUESTION = createWrappedSearchResult({
+  name: "My Item",
+  model: "card",
+  description: "My Item Description",
+  getIcon: () => ({ name: "table" }),
+});
+
+const TEST_RESULT_COLLECTION = createWrappedSearchResult({
+  name: "My Folder of Goodies",
+  model: "collection",
+  collection: TEST_REGULAR_COLLECTION,
+});
+
+const setup = ({ result }: { result: WrappedResult }) => {
+  setupCollectionByIdEndpoint({
+    collections: [TEST_REGULAR_COLLECTION],
+  });
+
+  setupUsersEndpoints([createMockUser()]);
+
+  renderWithProviders(<SearchResult result={result} />);
+};
+
+describe("SearchResult", () => {
+  it("renders a search result question item", () => {
+    setup({ result: TEST_RESULT_QUESTION });
+
+    expect(screen.getByText(TEST_RESULT_QUESTION.name)).toBeInTheDocument();
+    expect(
+      screen.getByText(TEST_RESULT_QUESTION.description as string),
+    ).toBeInTheDocument();
+    expect(getIcon("table")).toBeInTheDocument();
+  });
+
+  it("renders a search result collection item", () => {
+    setup({ result: TEST_RESULT_COLLECTION });
+
+    expect(screen.getByText(TEST_RESULT_COLLECTION.name)).toBeInTheDocument();
+    expect(screen.getByText("Collection")).toBeInTheDocument();
+    expect(
+      screen.queryByText(TEST_RESULT_COLLECTION.collection.name),
+    ).not.toBeInTheDocument();
+    expect(getIcon("folder")).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchResult/tests/util.ts b/frontend/src/metabase/search/components/SearchResult/tests/util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd86b06a58a43aaad84c121bae7cef02e7981429
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResult/tests/util.ts
@@ -0,0 +1,15 @@
+import { createMockSearchResult } from "metabase-types/api/mocks";
+import type { WrappedResult } from "metabase/search/types";
+
+export const createWrappedSearchResult = (
+  options: Partial<WrappedResult>,
+): WrappedResult => {
+  const result = createMockSearchResult(options);
+
+  return {
+    ...result,
+    getUrl: options.getUrl ?? (() => "/collection/root"),
+    getIcon: options.getIcon ?? (() => ({ name: "folder" })),
+    getCollection: options.getCollection ?? (() => result.collection),
+  };
+};
diff --git a/frontend/src/metabase/search/components/SearchResultLink/SearchResultLink.styled.tsx b/frontend/src/metabase/search/components/SearchResultLink/SearchResultLink.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..966e6ffd114243fbf736146b71839d5fb592551d
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResultLink/SearchResultLink.styled.tsx
@@ -0,0 +1,30 @@
+import { css } from "@emotion/react";
+import styled from "@emotion/styled";
+import type { TextProps, AnchorProps } from "metabase/ui";
+import { Group } from "metabase/ui";
+
+type ResultLinkProps = AnchorProps | TextProps;
+
+export const ResultLink = styled.a<ResultLinkProps>`
+  line-height: unset;
+
+  ${({ theme, href }) => {
+    return (
+      href &&
+      css`
+        &:hover,
+        &:focus,
+        &:focus-within {
+          color: ${theme.colors.brand[1]};
+          outline: 0;
+        }
+      `
+    );
+  }};
+
+  transition: color 0.2s ease-in-out;
+`;
+
+export const ResultLinkWrapper = styled(Group)`
+  overflow: hidden;
+`;
diff --git a/frontend/src/metabase/search/components/SearchResultLink/SearchResultLink.tsx b/frontend/src/metabase/search/components/SearchResultLink/SearchResultLink.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..abe436408fb003cc65f92db4f94785c4cede072d
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResultLink/SearchResultLink.tsx
@@ -0,0 +1,47 @@
+import Tooltip from "metabase/core/components/Tooltip";
+import { Anchor, Text } from "metabase/ui";
+import { useIsTruncated } from "metabase/hooks/use-is-truncated";
+import { ResultLink, ResultLinkWrapper } from "./SearchResultLink.styled";
+
+export const SearchResultLink = ({
+  children,
+  leftIcon = null,
+  href = null,
+}: {
+  children: JSX.Element | string | null;
+  leftIcon?: JSX.Element | null;
+  href?: string | null;
+}) => {
+  const { isTruncated, ref: truncatedRef } =
+    useIsTruncated<HTMLAnchorElement>();
+
+  const componentProps = href
+    ? {
+        as: Anchor,
+        href,
+        td: "underline",
+      }
+    : {
+        as: Text,
+        td: "none",
+      };
+
+  return (
+    <Tooltip isEnabled={isTruncated} tooltip={children}>
+      <ResultLinkWrapper data-testid="result-link-wrapper" spacing="xs" noWrap>
+        {leftIcon}
+        <ResultLink
+          {...componentProps}
+          span
+          c="text.1"
+          size="sm"
+          truncate
+          onClick={e => e.stopPropagation()}
+          ref={truncatedRef}
+        >
+          {children}
+        </ResultLink>
+      </ResultLinkWrapper>
+    </Tooltip>
+  );
+};
diff --git a/frontend/src/metabase/search/components/SearchResultLink/index.ts b/frontend/src/metabase/search/components/SearchResultLink/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8bb8a54f2b7bf9bd6822e9459e79166a67516d4c
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchResultLink/index.ts
@@ -0,0 +1 @@
+export * from "./SearchResultLink";
diff --git a/frontend/src/metabase/search/components/SearchSidebar/SearchSidebar.tsx b/frontend/src/metabase/search/components/SearchSidebar/SearchSidebar.tsx
index e3cafc2b79ce8ba6f6014a789f5180a838548634..099103155af1a14550e009d9a2ca8689087cf2e2 100644
--- a/frontend/src/metabase/search/components/SearchSidebar/SearchSidebar.tsx
+++ b/frontend/src/metabase/search/components/SearchSidebar/SearchSidebar.tsx
@@ -1,35 +1,43 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
 import _ from "underscore";
 import type {
   FilterTypeKeys,
-  SearchFilterPropTypes,
-  SearchFilters,
-  SearchSidebarFilterComponent,
+  SearchFilterComponent,
+  SearchQueryParamValue,
+  URLSearchFilterQueryParams,
 } from "metabase/search/types";
-import { Title, Flex } from "metabase/ui";
+import { Stack } from "metabase/ui";
 import { SearchFilterKeys } from "metabase/search/constants";
-import { SidebarFilter } from "metabase/search/components/SidebarFilter/SidebarFilter";
-import { TypeFilter } from "metabase/search/components/filters/TypeFilter/TypeFilter";
+import { DropdownSidebarFilter } from "metabase/search/components/DropdownSidebarFilter";
+import { TypeFilter } from "metabase/search/components/filters/TypeFilter";
+import { PLUGIN_CONTENT_VERIFICATION } from "metabase/plugins";
+import { ToggleSidebarFilter } from "metabase/search/components/ToggleSidebarFilter";
+import { CreatedByFilter } from "metabase/search/components/filters/CreatedByFilter";
+import { NativeQueryFilter } from "metabase/search/components/filters/NativeQueryFilter";
+import { LastEditedByFilter } from "metabase/search/components/filters/LastEditedByFilter";
+import { LastEditedAtFilter } from "metabase/search/components/filters/LastEditedAtFilter";
+import { CreatedAtFilter } from "metabase/search/components/filters/CreatedAtFilter";
 
-export const filterMap: Record<FilterTypeKeys, SearchSidebarFilterComponent> = {
-  [SearchFilterKeys.Type]: TypeFilter,
+type SearchSidebarProps = {
+  value: URLSearchFilterQueryParams;
+  onChange: (value: URLSearchFilterQueryParams) => void;
 };
 
-export const SearchSidebar = ({
-  value,
-  onChangeFilters,
-}: {
-  value: SearchFilters;
-  onChangeFilters: (filters: SearchFilters) => void;
-}) => {
-  const onOutputChange = (
-    key: FilterTypeKeys,
-    val: SearchFilterPropTypes[FilterTypeKeys],
-  ) => {
-    if (!val || val.length === 0) {
-      onChangeFilters(_.omit(value, key));
+export const SearchSidebar = ({ value, onChange }: SearchSidebarProps) => {
+  const filterMap: Record<FilterTypeKeys, SearchFilterComponent> = {
+    [SearchFilterKeys.Type]: TypeFilter,
+    [SearchFilterKeys.CreatedBy]: CreatedByFilter,
+    [SearchFilterKeys.CreatedAt]: CreatedAtFilter,
+    [SearchFilterKeys.LastEditedBy]: LastEditedByFilter,
+    [SearchFilterKeys.LastEditedAt]: LastEditedAtFilter,
+    [SearchFilterKeys.Verified]: PLUGIN_CONTENT_VERIFICATION.VerifiedFilter,
+    [SearchFilterKeys.NativeQuery]: NativeQueryFilter,
+  };
+
+  const onOutputChange = (key: FilterTypeKeys, val?: SearchQueryParamValue) => {
+    if (!val) {
+      onChange(_.omit(value, key));
     } else {
-      onChangeFilters({
+      onChange({
         ...value,
         [key]: val,
       });
@@ -37,18 +45,49 @@ export const SearchSidebar = ({
   };
 
   const getFilter = (key: FilterTypeKeys) => {
-    const Filter = filterMap[key];
-    const normalizedValue =
-      Array.isArray(value[key]) || !value[key] ? value[key] : [value[key]];
-    return (
-      <SidebarFilter
-        filter={Filter}
-        data-testid={`${key}-search-filter`}
-        value={normalizedValue}
-        onChange={value => onOutputChange(key, value)}
-      />
-    );
+    const Filter: SearchFilterComponent = filterMap[key];
+
+    if (!Filter.type) {
+      return null;
+    }
+
+    const filterValue = Filter.fromUrl(value[key]);
+
+    if (Filter.type === "toggle") {
+      return (
+        <ToggleSidebarFilter
+          filter={Filter}
+          value={filterValue}
+          data-testid={`${key}-search-filter`}
+          onChange={value => onOutputChange(key, Filter.toUrl(value))}
+        />
+      );
+    } else if (Filter.type === "dropdown") {
+      return (
+        <DropdownSidebarFilter
+          filter={Filter}
+          data-testid={`${key}-search-filter`}
+          value={filterValue}
+          onChange={value => onOutputChange(key, Filter.toUrl(value))}
+        />
+      );
+    }
+    return null;
   };
 
-  return <Flex direction="column">{getFilter(SearchFilterKeys.Type)}</Flex>;
+  return (
+    <Stack spacing="lg">
+      {getFilter(SearchFilterKeys.Type)}
+      <Stack spacing="sm">
+        {getFilter(SearchFilterKeys.CreatedBy)}
+        {getFilter(SearchFilterKeys.LastEditedBy)}
+      </Stack>
+      <Stack spacing="sm">
+        {getFilter(SearchFilterKeys.CreatedAt)}
+        {getFilter(SearchFilterKeys.LastEditedAt)}
+      </Stack>
+      {getFilter(SearchFilterKeys.Verified)}
+      {getFilter(SearchFilterKeys.NativeQuery)}
+    </Stack>
+  );
 };
diff --git a/frontend/src/metabase/search/components/SearchSidebar/index.ts b/frontend/src/metabase/search/components/SearchSidebar/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..37510d01e362dbce1a34ec056a3067f7f13a25b7
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchSidebar/index.ts
@@ -0,0 +1 @@
+export { SearchSidebar } from "./SearchSidebar";
diff --git a/frontend/src/metabase/search/components/SearchSidebar/tests/enterprise.unit.spec.tsx b/frontend/src/metabase/search/components/SearchSidebar/tests/enterprise.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bb4ce89b2a1a847fd1ec290a523c8ed6c588bbea
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchSidebar/tests/enterprise.unit.spec.tsx
@@ -0,0 +1,20 @@
+import { screen } from "__support__/ui";
+import type { SearchSidebarSetupOptions } from "./setup";
+import { setup } from "./setup";
+
+const setupEnterprise = async (opts?: SearchSidebarSetupOptions) => {
+  setup({
+    ...opts,
+    hasEnterprisePlugins: true,
+  });
+};
+
+describe("SearchFilterSidebar", () => {
+  it("should not render `Verified` filter when content_verification plugin is not enabled", async () => {
+    await setupEnterprise();
+
+    expect(
+      screen.queryByTestId("verified-search-filter"),
+    ).not.toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchSidebar/tests/premium.unit.spec.tsx b/frontend/src/metabase/search/components/SearchSidebar/tests/premium.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b39da7ea90566bc37f704dea454eb5245b80cf07
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchSidebar/tests/premium.unit.spec.tsx
@@ -0,0 +1,20 @@
+import { createMockTokenFeatures } from "metabase-types/api/mocks";
+import { screen } from "__support__/ui";
+import type { SearchSidebarSetupOptions } from "metabase/search/components/SearchSidebar/tests/setup";
+import { setup } from "metabase/search/components/SearchSidebar/tests/setup";
+
+const setupPremium = async (opts?: SearchSidebarSetupOptions) => {
+  setup({
+    ...opts,
+    tokenFeatures: createMockTokenFeatures({ content_verification: true }),
+    hasEnterprisePlugins: true,
+  });
+};
+
+describe("SearchFilterSidebar", () => {
+  it("renders `Verified` filter", async () => {
+    await setupPremium();
+
+    expect(screen.getByTestId("verified-search-filter")).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchSidebar/tests/setup.tsx b/frontend/src/metabase/search/components/SearchSidebar/tests/setup.tsx
index 443f072a6204b9645525f8b415168611bd8a1d1a..29a5772b6412ff8a7ca0852dbff535ca7cf644ed 100644
--- a/frontend/src/metabase/search/components/SearchSidebar/tests/setup.tsx
+++ b/frontend/src/metabase/search/components/SearchSidebar/tests/setup.tsx
@@ -1,11 +1,44 @@
+import { setupDatabasesEndpoints } from "__support__/server-mocks";
 import { renderWithProviders } from "__support__/ui";
-import { SearchSidebar } from "metabase/search/components/SearchSidebar/SearchSidebar";
+import {
+  createMockDatabase,
+  createMockTokenFeatures,
+} from "metabase-types/api/mocks";
+import { setupEnterprisePlugins } from "__support__/enterprise";
+import { createMockState } from "metabase-types/store/mocks";
+import { mockSettings } from "__support__/settings";
+import type { TokenFeatures } from "metabase-types/api";
+import type { URLSearchFilterQueryParams } from "metabase/search/types";
+import { SearchSidebar } from "metabase/search/components/SearchSidebar";
 
-export const setup = ({ value = {}, onChangeFilters = jest.fn() } = {}) => {
-  const defaultProps = {
-    value,
-    onChangeFilters,
-  };
+export interface SearchSidebarSetupOptions {
+  tokenFeatures?: TokenFeatures;
+  hasEnterprisePlugins?: boolean;
+  value?: URLSearchFilterQueryParams;
+  onChange?: (filters: URLSearchFilterQueryParams) => void;
+}
 
-  renderWithProviders(<SearchSidebar {...defaultProps} />);
+const TEST_DATABASE = createMockDatabase();
+
+export const setup = ({
+  tokenFeatures = createMockTokenFeatures(),
+  hasEnterprisePlugins = false,
+  value = {},
+  onChange = jest.fn(),
+}: SearchSidebarSetupOptions = {}) => {
+  setupDatabasesEndpoints([TEST_DATABASE]);
+
+  const settings = mockSettings({ "token-features": tokenFeatures });
+
+  const state = createMockState({
+    settings,
+  });
+
+  if (hasEnterprisePlugins) {
+    setupEnterprisePlugins();
+  }
+
+  renderWithProviders(<SearchSidebar value={value} onChange={onChange} />, {
+    storeInitialState: state,
+  });
 };
diff --git a/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.styled.tsx b/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bf3aba377499c2e800669069b7bee5a8c8c7418b
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.styled.tsx
@@ -0,0 +1,46 @@
+import styled from "@emotion/styled";
+import { css } from "@emotion/react";
+import type { HTMLAttributes } from "react";
+import type { ButtonProps } from "metabase/ui";
+import { Stack, Button, Group, TextInput } from "metabase/ui";
+
+export const SearchUserPickerContainer = styled(Stack)`
+  overflow: hidden;
+`;
+
+export const SearchUserItemContainer = styled(Group)`
+  overflow-y: auto;
+`;
+
+export const UserPickerInput = styled(TextInput)`
+  flex: 1;
+`;
+
+export const SearchUserPickerContent = styled(Stack)`
+  overflow-y: auto;
+  flex: 1;
+`;
+
+export const SearchUserSelectBox = styled(Stack)`
+  border: ${({ theme }) => theme.colors.border[0]} 1px solid;
+  border-radius: ${({ theme }) => theme.radius.md};
+`;
+
+export const SelectedUserButton = styled(Button)<
+  ButtonProps & HTMLAttributes<HTMLButtonElement>
+>`
+  ${({ theme }) => {
+    const primaryColor = theme.colors.brand[1];
+    const backgroundColor = theme.fn.lighten(primaryColor, 0.8);
+    const hoverBackgroundColor = theme.fn.lighten(primaryColor, 0.6);
+
+    return css`
+      background-color: ${backgroundColor};
+      border: 0;
+
+      &:hover {
+        background-color: ${hoverBackgroundColor};
+      }
+    `;
+  }}
+`;
diff --git a/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.tsx b/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..20546e2215dcf623a482897ea52050f4c8e863ab
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.tsx
@@ -0,0 +1,132 @@
+import { without } from "underscore";
+import { useState } from "react";
+import { t } from "ttag";
+import type { UserId, UserListResult } from "metabase-types/api";
+import { Center, Text } from "metabase/ui";
+import { SearchFilterPopoverWrapper } from "metabase/search/components/SearchFilterPopoverWrapper";
+import {
+  SearchUserItemContainer,
+  SearchUserPickerContainer,
+  SearchUserPickerContent,
+  SearchUserSelectBox,
+  SelectedUserButton,
+  UserPickerInput,
+} from "metabase/search/components/SearchUserPicker/SearchUserPicker.styled";
+import { useUserListQuery } from "metabase/common/hooks/use-user-list-query";
+import { UserListElement } from "metabase/search/components/UserListElement";
+import { Icon } from "metabase/core/components/Icon";
+
+export const SearchUserPicker = ({
+  value,
+  onChange,
+}: {
+  value: UserId[];
+  onChange: (value: UserId[]) => void;
+}) => {
+  const { data: users = [], isLoading } = useUserListQuery();
+
+  const [userFilter, setUserFilter] = useState("");
+  const [selectedUserIds, setSelectedUserIds] = useState(value);
+
+  const isSelected = (user: UserListResult) =>
+    selectedUserIds.includes(user.id);
+
+  const filteredUsers = users.filter(user => {
+    return (
+      user.common_name.toLowerCase().includes(userFilter.toLowerCase()) &&
+      !isSelected(user)
+    );
+  });
+
+  const removeUser = (user?: UserListResult) => {
+    if (user) {
+      setSelectedUserIds(without(selectedUserIds, user.id));
+    }
+  };
+
+  const addUser = (user: UserListResult) => {
+    setSelectedUserIds([...selectedUserIds, user.id]);
+  };
+
+  const onUserSelect = (user: UserListResult) => {
+    if (isSelected(user)) {
+      removeUser(user);
+    } else {
+      addUser(user);
+    }
+  };
+
+  const generateUserListElements = (userList: UserListResult[]) => {
+    return userList.map(user => (
+      <UserListElement
+        key={user.id}
+        isSelected={isSelected(user)}
+        onClick={onUserSelect}
+        value={user}
+      />
+    ));
+  };
+
+  return (
+    <SearchFilterPopoverWrapper
+      isLoading={isLoading}
+      onApply={() => onChange(selectedUserIds)}
+    >
+      <SearchUserPickerContainer p="sm" spacing="xs">
+        <SearchUserSelectBox spacing={0}>
+          <SearchUserItemContainer
+            data-testid="search-user-select-box"
+            spacing="xs"
+            p="xs"
+            mah="30vh"
+          >
+            {selectedUserIds.map(userId => {
+              const user = users.find(user => user.id === userId);
+              return (
+                <SelectedUserButton
+                  data-testid="selected-user-button"
+                  key={userId}
+                  c="brand.1"
+                  px="md"
+                  py="sm"
+                  maw="100%"
+                  rightIcon={<Icon name="close" />}
+                  onClick={() => removeUser(user)}
+                >
+                  <Text align="left" w="100%" truncate c="inherit">
+                    {user?.common_name}
+                  </Text>
+                </SelectedUserButton>
+              );
+            })}
+            <UserPickerInput
+              variant="unstyled"
+              pl="sm"
+              size="md"
+              placeholder={t`Search for someone…`}
+              value={userFilter}
+              tabIndex={0}
+              onChange={event => setUserFilter(event.currentTarget.value)}
+              mt="-0.25rem"
+              miw="18ch"
+            />
+          </SearchUserItemContainer>
+        </SearchUserSelectBox>
+        <SearchUserPickerContent
+          data-testid="search-user-list"
+          h="100%"
+          spacing="xs"
+          p="xs"
+        >
+          {filteredUsers.length > 0 ? (
+            generateUserListElements(filteredUsers)
+          ) : (
+            <Center py="md">
+              <Text size="md" weight={700}>{t`No results`}</Text>
+            </Center>
+          )}
+        </SearchUserPickerContent>
+      </SearchUserPickerContainer>
+    </SearchFilterPopoverWrapper>
+  );
+};
diff --git a/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.unit.spec.tsx b/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..721ed1069381fbd8f60add264f95553462981072
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchUserPicker/SearchUserPicker.unit.spec.tsx
@@ -0,0 +1,163 @@
+import userEvent from "@testing-library/user-event";
+import { useState } from "react";
+import { createMockUser } from "metabase-types/api/mocks";
+import type { User, UserId } from "metabase-types/api";
+import { screen, renderWithProviders, waitFor, within } from "__support__/ui";
+import type { CreatedByFilterProps } from "metabase/search/types";
+import { setupUsersEndpoints } from "__support__/server-mocks";
+import { SearchUserPicker } from "metabase/search/components/SearchUserPicker";
+
+const TEST_USERS: User[] = [
+  createMockUser({ id: 1, common_name: "Alice" }),
+  createMockUser({ id: 2, common_name: "Bob" }),
+];
+
+const TestSearchUserPicker = ({
+  value,
+  onChange,
+}: {
+  value: UserId[];
+  onChange: jest.Func;
+}) => {
+  const [selectedUserIds, setSelectedUserIds] =
+    useState<CreatedByFilterProps>(value);
+  const onUserChange = (userIds: CreatedByFilterProps) => {
+    setSelectedUserIds(userIds);
+    onChange(userIds);
+  };
+  return <SearchUserPicker value={selectedUserIds} onChange={onUserChange} />;
+};
+
+const setup = async ({
+  initialSelectedUsers = [],
+}: { initialSelectedUsers?: UserId[] } = {}) => {
+  setupUsersEndpoints(TEST_USERS);
+
+  const mockOnChange = jest.fn();
+  renderWithProviders(
+    <TestSearchUserPicker
+      value={initialSelectedUsers}
+      onChange={mockOnChange}
+    />,
+  );
+
+  await waitFor(() => {
+    expect(screen.queryByTestId("loading-spinner")).not.toBeInTheDocument();
+  });
+
+  return { mockOnChange };
+};
+
+describe("SearchUserPicker", () => {
+  it("should display user list when data is available", async () => {
+    await setup();
+
+    expect(screen.getByText("Alice")).toBeInTheDocument();
+    expect(screen.getByText("Bob")).toBeInTheDocument();
+  });
+
+  it("should show 'No results' when no users match the filter", async () => {
+    await setup();
+    userEvent.type(
+      screen.getByPlaceholderText("Search for someone…"),
+      "Charlie",
+    );
+    expect(screen.getByText("No results")).toBeInTheDocument();
+  });
+
+  it("should show selected users in the select box on initial load", async () => {
+    await setup({ initialSelectedUsers: TEST_USERS.map(user => user.id) });
+    expect(
+      screen.getAllByTestId("selected-user-button").map(el => el.textContent),
+    ).toEqual(["Alice", "Bob"]);
+
+    // all users are in the select box, so the search list should be empty
+    expect(screen.getByText("No results")).toBeInTheDocument();
+  });
+
+  it("should not show any users when there are no selected users on initial load", async () => {
+    await setup();
+    expect(
+      screen.queryByTestId("selected-user-button"),
+    ).not.toBeInTheDocument();
+  });
+
+  it("should add user to select box and remove them from search list when user is selected from search list", async () => {
+    await setup();
+    const searchUserList = within(screen.getByTestId("search-user-list"));
+
+    userEvent.click(searchUserList.getByText("Alice"));
+    expect(screen.getByTestId("selected-user-button")).toHaveTextContent(
+      "Alice",
+    );
+
+    expect(searchUserList.queryByText("Alice")).not.toBeInTheDocument();
+
+    userEvent.click(searchUserList.getByText("Bob"));
+    expect(
+      screen.getAllByTestId("selected-user-button").map(el => el.textContent),
+    ).toEqual(["Alice", "Bob"]);
+
+    expect(searchUserList.getByText("No results")).toBeInTheDocument();
+  });
+
+  it("should remove user from select box and add them to search list when user is remove from select box", async () => {
+    await setup({
+      initialSelectedUsers: TEST_USERS.map(user => user.id),
+    });
+
+    const searchUserList = within(screen.getByTestId("search-user-list"));
+    const selectBox = within(screen.getByTestId("search-user-select-box"));
+
+    // expect the two users are in the select box and not in the search list
+    expect(searchUserList.getByText("No results")).toBeInTheDocument();
+    expect(selectBox.getByText("Alice")).toBeInTheDocument();
+    expect(selectBox.getByText("Bob")).toBeInTheDocument();
+
+    userEvent.click(selectBox.getByText("Alice"));
+    expect(screen.getByTestId("selected-user-button")).toHaveTextContent("Bob");
+    expect(searchUserList.getByText("Alice")).toBeInTheDocument();
+
+    userEvent.click(selectBox.getByText("Bob"));
+
+    // expect the two users are only in the search list now
+    expect(
+      screen.queryByTestId("selected-user-button"),
+    ).not.toBeInTheDocument();
+    expect(searchUserList.getByText("Alice")).toBeInTheDocument();
+    expect(searchUserList.getByText("Bob")).toBeInTheDocument();
+  });
+
+  it("should filter users when user types in the search box", async () => {
+    await setup();
+    userEvent.type(screen.getByPlaceholderText("Search for someone…"), "Alice");
+    const searchUserList = within(screen.getByTestId("search-user-list"));
+    expect(searchUserList.getByText("Alice")).toBeInTheDocument();
+
+    expect(searchUserList.queryByText("Bob")).not.toBeInTheDocument();
+  });
+
+  it("should call onChange with a list of user ids when the user clicks Apply with a selection", async () => {
+    const { mockOnChange } = await setup();
+
+    const searchUserList = within(screen.getByTestId("search-user-list"));
+    userEvent.click(searchUserList.getByText("Alice"));
+    userEvent.click(searchUserList.getByText("Bob"));
+
+    userEvent.click(screen.getByText("Apply"));
+
+    expect(mockOnChange).toHaveBeenCalledWith([1, 2]);
+  });
+
+  it("should call onChange with an empty list when the user clicks Apply with no selection", async () => {
+    const { mockOnChange } = await setup({
+      initialSelectedUsers: TEST_USERS.map(user => user.id),
+    });
+    const searchUserList = within(screen.getByTestId("search-user-select-box"));
+    userEvent.click(searchUserList.getByText("Alice"));
+    userEvent.click(searchUserList.getByText("Bob"));
+
+    userEvent.click(screen.getByText("Apply"));
+    expect(mockOnChange).toHaveBeenCalledWith([]);
+  });
+});
diff --git a/frontend/src/metabase/search/components/SearchUserPicker/index.ts b/frontend/src/metabase/search/components/SearchUserPicker/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ace4802c368a832441cd98cb922c9cec4e229a3b
--- /dev/null
+++ b/frontend/src/metabase/search/components/SearchUserPicker/index.ts
@@ -0,0 +1,2 @@
+export { SearchUserPicker } from "./SearchUserPicker";
+export * from "./SearchUserPicker.styled";
diff --git a/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.styled.tsx b/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.styled.tsx
deleted file mode 100644
index e659f438622fee90fce121b484f5e98f4347abb4..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.styled.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import styled from "@emotion/styled";
-import { css } from "@emotion/react";
-import { ParameterFieldSet } from "metabase/parameters/components/ParameterWidget/ParameterWidget.styled";
-
-export const DropdownFilterElement = styled(ParameterFieldSet)`
-  height: 40px;
-
-  ${({ theme, fieldHasValueOrFocus }) => {
-    return (
-      fieldHasValueOrFocus &&
-      css`
-        border-color: ${theme.colors.brand[1]};
-        color: ${theme.colors.brand[1]};
-      `
-    );
-  }}
-  &:hover {
-    ${({ theme }) => {
-      return css`
-        background-color: ${theme.colors?.bg[1]};
-        transition: background-color 0.3s;
-        cursor: pointer;
-      `;
-    }}
-  }
-
-  @media screen and (min-width: 440px) {
-    margin-right: 0;
-  }
-`;
-export const DropdownApplyButtonDivider = styled.hr`
-  ${({ theme }) => {
-    return css`
-      border-top: 1px solid ${theme.colors?.border[0]};
-    `;
-  }}
-`;
diff --git a/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.tsx b/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.tsx
deleted file mode 100644
index ed65e6ba628a9ebafb11c559d665b03752de7f9e..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/search/components/SidebarFilter/SidebarFilter.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { isEmpty } from "underscore";
-import type { MouseEvent } from "react";
-import { useLayoutEffect, useRef, useState } from "react";
-import { t } from "ttag";
-import type {
-  SearchFilterComponentProps,
-  SearchSidebarFilterComponent,
-} from "metabase/search/types";
-import { Box, Button, Group, Text } from "metabase/ui";
-import type { IconName } from "metabase/core/components/Icon";
-import { Icon } from "metabase/core/components/Icon";
-import Popover from "metabase/components/Popover";
-import { useSelector } from "metabase/lib/redux";
-import { getIsNavbarOpen } from "metabase/selectors/app";
-import useIsSmallScreen from "metabase/hooks/use-is-small-screen";
-import {
-  DropdownApplyButtonDivider,
-  DropdownFilterElement,
-} from "./SidebarFilter.styled";
-
-export type SearchSidebarFilterProps = {
-  filter: SearchSidebarFilterComponent;
-} & SearchFilterComponentProps;
-
-export const SidebarFilter = ({
-  filter: { title, iconName, DisplayComponent, ContentComponent },
-  "data-testid": dataTestId,
-  value,
-  onChange,
-}: SearchSidebarFilterProps) => {
-  const [selectedValues, setSelectedValues] = useState(value);
-  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
-
-  const isNavbarOpen = useSelector(getIsNavbarOpen);
-  const isSmallScreen = useIsSmallScreen();
-
-  const dropdownRef = useRef<HTMLDivElement>(null);
-  const [popoverWidth, setPopoverWidth] = useState<string | null>(null);
-
-  const fieldHasValue = !isEmpty(value);
-
-  const handleResize = () => {
-    if (dropdownRef.current) {
-      const { width } = dropdownRef.current.getBoundingClientRect();
-      setPopoverWidth(`${width}px`);
-    }
-  };
-
-  useLayoutEffect(() => {
-    if (!popoverWidth) {
-      handleResize();
-    }
-    window.addEventListener("resize", handleResize, false);
-    return () => window.removeEventListener("resize", handleResize, false);
-  }, [dropdownRef, popoverWidth]);
-
-  useLayoutEffect(() => {
-    if (isNavbarOpen && isSmallScreen) {
-      setIsPopoverOpen(false);
-    }
-  }, [isNavbarOpen, isSmallScreen]);
-
-  const onApplyFilter = () => {
-    onChange(selectedValues);
-    setIsPopoverOpen(false);
-  };
-
-  const onClearFilter = (e: MouseEvent) => {
-    if (fieldHasValue) {
-      e.stopPropagation();
-      setSelectedValues(undefined);
-      onChange(undefined);
-      setIsPopoverOpen(false);
-    }
-  };
-
-  const onPopoverClose = () => {
-    // reset selection to the current filter state
-    setSelectedValues(value);
-    setIsPopoverOpen(false);
-  };
-
-  const getDropdownIcon = (): IconName => {
-    if (fieldHasValue) {
-      return "close";
-    } else {
-      return isPopoverOpen ? "chevronup" : "chevrondown";
-    }
-  };
-
-  return (
-    <div data-testid={dataTestId} ref={dropdownRef}>
-      <div onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
-        <DropdownFilterElement
-          noPadding
-          fieldHasValueOrFocus={fieldHasValue}
-          legend={fieldHasValue ? title : null}
-        >
-          <Group position="apart">
-            {fieldHasValue ? (
-              <DisplayComponent value={value} />
-            ) : (
-              <Group noWrap>
-                <Icon name={iconName} />
-                <Text weight={700}>{title}</Text>
-              </Group>
-            )}
-            <Button
-              style={{ pointerEvents: "all" }}
-              data-testid="sidebar-filter-dropdown-button"
-              compact
-              c="inherit"
-              variant="subtle"
-              onClick={onClearFilter}
-              leftIcon={<Icon name={getDropdownIcon()} />}
-            />
-          </Group>
-        </DropdownFilterElement>
-      </div>
-      <Popover
-        isOpen={isPopoverOpen}
-        onClose={onPopoverClose}
-        target={dropdownRef.current}
-        ignoreTrigger
-        autoWidth
-      >
-        <Box p="md" w={popoverWidth ?? "100%"}>
-          <ContentComponent
-            value={selectedValues}
-            onChange={selected => setSelectedValues(selected)}
-          />
-        </Box>
-        <DropdownApplyButtonDivider />
-        <Group position="right" align="center" px="sm" pb="sm">
-          <Button onClick={onApplyFilter}>{t`Apply filters`}</Button>
-        </Group>
-      </Popover>
-    </div>
-  );
-};
diff --git a/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.styled.tsx b/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0130ef7aa8adf4df16746d418d0abe5be816c6d6
--- /dev/null
+++ b/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.styled.tsx
@@ -0,0 +1,8 @@
+import styled from "@emotion/styled";
+import { Switch } from "metabase/ui";
+
+export const FilterSwitch = styled(Switch)`
+  .emotion-Switch-body {
+    justify-content: space-between;
+  }
+`;
diff --git a/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.tsx b/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2dc4b7aa83d29b7797507a7c0eb0e2c5d1731d83
--- /dev/null
+++ b/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.tsx
@@ -0,0 +1,32 @@
+import type { SearchFilterToggle } from "metabase/search/types";
+import { Text } from "metabase/ui";
+import { FilterSwitch } from "./ToggleSidebarFilter.styled";
+
+export type ToggleSidebarFilterProps = {
+  filter: SearchFilterToggle;
+  value: boolean;
+  onChange: (value: boolean) => void;
+  "data-testid"?: string;
+};
+
+export const ToggleSidebarFilter = ({
+  filter: { label },
+  value,
+  onChange,
+  "data-testid": dataTestId,
+}: ToggleSidebarFilterProps) => {
+  return (
+    <FilterSwitch
+      wrapperProps={{
+        "data-testid": dataTestId,
+      }}
+      data-testid="toggle-filter-switch"
+      size="sm"
+      labelPosition="left"
+      label={<Text color="text.2">{label()}</Text>}
+      data-is-checked={value}
+      checked={value}
+      onChange={event => onChange(event.currentTarget.checked)}
+    />
+  );
+};
diff --git a/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.unit.spec.tsx b/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..92ffec7bac0232a2d92cee535144876a457aac42
--- /dev/null
+++ b/frontend/src/metabase/search/components/ToggleSidebarFilter/ToggleSidebarFilter.unit.spec.tsx
@@ -0,0 +1,101 @@
+import userEvent from "@testing-library/user-event";
+import { useState } from "react";
+import { renderWithProviders, screen } from "__support__/ui";
+import type { SearchFilterComponent } from "metabase/search/types";
+import type { ToggleSidebarFilterProps } from "metabase/search/components/ToggleSidebarFilter";
+import { ToggleSidebarFilter } from "metabase/search/components/ToggleSidebarFilter";
+
+const mockFilter: SearchFilterComponent = {
+  label: () => "Mock Filter",
+  iconName: "filter",
+  type: "toggle",
+  fromUrl: value => value,
+  toUrl: value => value,
+};
+
+const MockToggleSidebarFilter = ({
+  filter,
+  value,
+  onChange,
+}: ToggleSidebarFilterProps) => {
+  const [toggleValue, setToggleValue] = useState(value);
+  const onFilterChange = (toggleValue: ToggleSidebarFilterProps["value"]) => {
+    setToggleValue(toggleValue);
+    onChange(toggleValue);
+  };
+
+  return (
+    <ToggleSidebarFilter
+      filter={filter}
+      value={toggleValue}
+      onChange={onFilterChange}
+    />
+  );
+};
+
+const setup = ({
+  value = false,
+  onChange = jest.fn(),
+}: {
+  value?: ToggleSidebarFilterProps["value"];
+  onChange?: jest.Mock;
+} = {}) => {
+  renderWithProviders(
+    <MockToggleSidebarFilter
+      filter={mockFilter}
+      value={value}
+      onChange={onChange}
+    />,
+  );
+
+  return {
+    onChange,
+  };
+};
+
+describe("ToggleSidebarFilter", () => {
+  it("should render the component with the title", () => {
+    setup({
+      onChange: jest.fn(),
+    });
+
+    const titleElement = screen.getByText("Mock Filter");
+    const switchElement = screen.getByTestId("toggle-filter-switch");
+
+    expect(titleElement).toBeInTheDocument();
+    expect(switchElement).toBeInTheDocument();
+  });
+
+  it("should call the onChange function when the switch is toggled", () => {
+    const onChangeMock = jest.fn();
+    setup({
+      value: undefined,
+      onChange: onChangeMock,
+    });
+
+    const switchElement = screen.getByRole("checkbox");
+    userEvent.click(switchElement);
+
+    expect(onChangeMock).toHaveBeenCalledTimes(1);
+    expect(onChangeMock).toHaveBeenCalledWith(true);
+
+    expect(switchElement).toHaveAttribute("data-is-checked", "true");
+  });
+
+  it("should have the switch checked when value is true", () => {
+    setup({
+      value: true,
+      onChange: jest.fn(),
+    });
+
+    const switchElement = screen.getByRole("checkbox");
+    expect(switchElement).toHaveAttribute("data-is-checked", "true");
+  });
+
+  it("should have the switch unchecked when value is false", () => {
+    setup();
+
+    const switchElement = screen.getByRole("checkbox");
+    expect(switchElement).toHaveAttribute("data-is-checked", "false");
+  });
+});
diff --git a/frontend/src/metabase/search/components/ToggleSidebarFilter/index.ts b/frontend/src/metabase/search/components/ToggleSidebarFilter/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..50400b24cdb00ffeb1c3f3256d846d96849abc62
--- /dev/null
+++ b/frontend/src/metabase/search/components/ToggleSidebarFilter/index.ts
@@ -0,0 +1,2 @@
+export { ToggleSidebarFilter } from "./ToggleSidebarFilter";
+export type { ToggleSidebarFilterProps } from "./ToggleSidebarFilter";
diff --git a/frontend/src/metabase/search/components/UserListElement/UserListElement.styled.tsx b/frontend/src/metabase/search/components/UserListElement/UserListElement.styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6a7e0510036e340fedf7e37934450c43444b31e0
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserListElement/UserListElement.styled.tsx
@@ -0,0 +1,17 @@
+import styled from "@emotion/styled";
+import type { HTMLAttributes } from "react";
+import type { ButtonProps } from "metabase/ui";
+import { Button } from "metabase/ui";
+
+export const UserElement = styled(Button)<
+  HTMLAttributes<HTMLButtonElement> & ButtonProps
+>`
+  &:hover {
+    background-color: ${({ theme }) => theme.colors.brand[0]};
+  }
+
+  & > div {
+    display: flex;
+    justify-content: flex-start;
+  }
+`;
diff --git a/frontend/src/metabase/search/components/UserListElement/UserListElement.tsx b/frontend/src/metabase/search/components/UserListElement/UserListElement.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..185ad0318ed2b88db0ab02cbaf23d573bfcf0cef
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserListElement/UserListElement.tsx
@@ -0,0 +1,29 @@
+import type { UserListResult } from "metabase-types/api";
+import { Text } from "metabase/ui";
+import { UserElement } from "./UserListElement.styled";
+
+export type UserListElementProps = {
+  value: UserListResult;
+  onClick: (value: UserListResult) => void;
+  isSelected: boolean;
+};
+
+export const UserListElement = ({
+  value,
+  isSelected,
+  onClick,
+}: UserListElementProps) => (
+  <UserElement
+    data-testid="user-list-element"
+    onClick={() => onClick(value)}
+    data-is-selected={isSelected}
+    px="sm"
+    py="xs"
+    variant="subtle"
+    bg={isSelected ? "brand.0" : undefined}
+  >
+    <Text weight={700} color={isSelected ? "brand.1" : undefined} truncate>
+      {value.common_name}
+    </Text>
+  </UserElement>
+);
diff --git a/frontend/src/metabase/search/components/UserListElement/UserListElement.unit.spec.tsx b/frontend/src/metabase/search/components/UserListElement/UserListElement.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..53de44c62da36925e7dff814c85bebe727ef23ec
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserListElement/UserListElement.unit.spec.tsx
@@ -0,0 +1,54 @@
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders, screen } from "__support__/ui";
+import { createMockUserListResult } from "metabase-types/api/mocks";
+import { UserListElement } from "metabase/search/components/UserListElement/index";
+
+const TEST_USER_LIST_RESULT = createMockUserListResult({
+  common_name: "Alice Johnson",
+});
+
+const setup = ({ value = TEST_USER_LIST_RESULT, isSelected = false } = {}) => {
+  const onClickMock = jest.fn();
+  renderWithProviders(
+    <UserListElement
+      value={value}
+      isSelected={isSelected}
+      onClick={onClickMock}
+    />,
+  );
+  return { onClickMock };
+};
+
+describe("UserListElement", () => {
+  it("should render the component with user's common name", () => {
+    setup();
+    expect(screen.getByTestId("user-list-element")).toHaveTextContent(
+      "Alice Johnson",
+    );
+  });
+
+  it("should call the onClick function when clicked", () => {
+    const { onClickMock } = setup();
+
+    userEvent.click(screen.getByText("Alice Johnson"));
+
+    expect(onClickMock).toHaveBeenCalledTimes(1);
+    expect(onClickMock).toHaveBeenCalledWith(TEST_USER_LIST_RESULT);
+  });
+
+  it("should be selected when isSelected is true", () => {
+    setup({ isSelected: true });
+    expect(screen.getByTestId("user-list-element")).toHaveAttribute(
+      "data-is-selected",
+      "true",
+    );
+  });
+
+  it("should not be selected when isSelected is false", () => {
+    setup();
+    expect(screen.getByTestId("user-list-element")).toHaveAttribute(
+      "data-is-selected",
+      "false",
+    );
+  });
+});
diff --git a/frontend/src/metabase/search/components/UserListElement/index.ts b/frontend/src/metabase/search/components/UserListElement/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cc3997ea6e6949abf035c3cf89af41bfec2a8b5e
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserListElement/index.ts
@@ -0,0 +1 @@
+export { UserListElement } from "./UserListElement";
diff --git a/frontend/src/metabase/search/components/UserNameDisplay/UserNameDisplay.tsx b/frontend/src/metabase/search/components/UserNameDisplay/UserNameDisplay.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f360c21076ff01b1298cd1d2c7f6913c9ca56ccc
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserNameDisplay/UserNameDisplay.tsx
@@ -0,0 +1,40 @@
+import { t } from "ttag";
+import { useUserListQuery } from "metabase/common/hooks/use-user-list-query";
+import { Text } from "metabase/ui";
+import type { UserId } from "metabase-types/api";
+
+export type UserNameDisplayProps = {
+  userIdList: UserId[];
+  label: string;
+};
+
+export const UserNameDisplay = ({
+  userIdList,
+  label,
+}: UserNameDisplayProps) => {
+  const { data: users = [], isLoading } = useUserListQuery();
+
+  const selectedUserList = users.filter(user => userIdList.includes(user.id));
+
+  const getDisplayValue = () => {
+    if (isLoading) {
+      return t`Loading…`;
+    }
+
+    if (selectedUserList.length === 0) {
+      return label;
+    }
+
+    if (selectedUserList.length === 1) {
+      return selectedUserList[0].common_name ?? t`1 user selected`;
+    }
+
+    return t`${selectedUserList.length} users selected`;
+  };
+
+  return (
+    <Text c="inherit" weight={700} truncate>
+      {getDisplayValue()}
+    </Text>
+  );
+};
diff --git a/frontend/src/metabase/search/components/UserNameDisplay/UserNameDisplay.unit.spec.tsx b/frontend/src/metabase/search/components/UserNameDisplay/UserNameDisplay.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0d2886de48777bad1bdeb53437fc97322b6ab2a3
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserNameDisplay/UserNameDisplay.unit.spec.tsx
@@ -0,0 +1,77 @@
+import { renderWithProviders, screen, waitFor } from "__support__/ui";
+import { createMockUserListResult } from "metabase-types/api/mocks";
+import { setupUsersEndpoints } from "__support__/server-mocks";
+import type { UserListResult } from "metabase-types/api";
+import { UserNameDisplay } from "./UserNameDisplay";
+import type { UserNameDisplayProps } from "./UserNameDisplay";
+
+const TEST_USER_LIST_RESULTS = [
+  createMockUserListResult({ id: 1, common_name: "Testy Tableton" }),
+  createMockUserListResult({ id: 2, common_name: "Testy McTestface" }),
+];
+
+const setup = async ({
+  userIdList = [],
+  users = TEST_USER_LIST_RESULTS,
+  waitForLoading = true,
+}: {
+  userIdList?: UserNameDisplayProps["userIdList"];
+  users?: UserListResult[];
+  waitForLoading?: boolean;
+} = {}) => {
+  setupUsersEndpoints(users);
+
+  renderWithProviders(
+    <UserNameDisplay label={"UserNameDisplay Test"} userIdList={userIdList} />,
+  );
+
+  if (waitForLoading) {
+    await waitFor(() => {
+      expect(screen.queryByText("Loading…")).not.toBeInTheDocument();
+    });
+  }
+};
+
+describe("UserNameDisplay", () => {
+  it("should initially display loading message when users are selected", async () => {
+    await setup({
+      waitForLoading: false,
+      userIdList: [TEST_USER_LIST_RESULTS[0].id],
+    });
+    expect(screen.getByText("Loading…")).toBeInTheDocument();
+  });
+
+  it("should initially display title when user list is empty", async () => {
+    await setup({ waitForLoading: true });
+    expect(screen.getByText("UserNameDisplay Test")).toBeInTheDocument();
+  });
+
+  it("should display user name when there's one user in the list", async () => {
+    await setup({ userIdList: [TEST_USER_LIST_RESULTS[0].id] });
+    expect(screen.getByText("Testy Tableton")).toBeInTheDocument();
+  });
+
+  it("should fallback to '1 user selected' if there is one user and they don't have a common name", async () => {
+    // the backend should always return a `common_name` field, so this is a fallback
+
+    const userWithoutCommonName = createMockUserListResult({
+      id: 99999,
+      common_name: undefined,
+    });
+
+    await setup({
+      users: [userWithoutCommonName],
+      userIdList: [userWithoutCommonName.id],
+    });
+    expect(screen.getByText("1 user selected")).toBeInTheDocument();
+  });
+
+  it("should display `X users selected` if there are multiple users", async () => {
+    await setup({
+      userIdList: TEST_USER_LIST_RESULTS.map(user => user.id),
+    });
+    expect(
+      screen.getByText(`${TEST_USER_LIST_RESULTS.length} users selected`),
+    ).toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/search/components/UserNameDisplay/index.ts b/frontend/src/metabase/search/components/UserNameDisplay/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee8832407f5c66d9523b91ab023afda34f3fe227
--- /dev/null
+++ b/frontend/src/metabase/search/components/UserNameDisplay/index.ts
@@ -0,0 +1,2 @@
+export { UserNameDisplay } from "./UserNameDisplay";
+export type { UserNameDisplayProps } from "./UserNameDisplay";
diff --git a/frontend/src/metabase/search/components/filters/CreatedAtFilter.tsx b/frontend/src/metabase/search/components/filters/CreatedAtFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e60f1dae8f8c45c80404b70df05d79937e75e274
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/CreatedAtFilter.tsx
@@ -0,0 +1,25 @@
+/* eslint-disable react/prop-types */
+import { t } from "ttag";
+import { Box } from "metabase/ui";
+import { SearchFilterDateDisplay } from "metabase/search/components/SearchFilterDateDisplay";
+import { SearchFilterDatePicker } from "metabase/search/components/SearchFilterDatePicker";
+import type { SearchFilterDropdown } from "metabase/search/types";
+
+export const CreatedAtFilter: SearchFilterDropdown<"created_at"> = {
+  iconName: "calendar",
+  label: () => t`Creation date`,
+  type: "dropdown",
+  DisplayComponent: ({ value: dateString }) => (
+    <SearchFilterDateDisplay
+      label={CreatedAtFilter.label()}
+      value={dateString}
+    />
+  ),
+  ContentComponent: ({ value, onChange, width }) => (
+    <Box miw={width} maw="30rem">
+      <SearchFilterDatePicker value={value} onChange={onChange} />
+    </Box>
+  ),
+  fromUrl: value => value,
+  toUrl: value => value,
+};
diff --git a/frontend/src/metabase/search/components/filters/CreatedByFilter/CreatedByFilter.tsx b/frontend/src/metabase/search/components/filters/CreatedByFilter/CreatedByFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..be109de62c6c57c3542ee27e4de54a01b84c1ae6
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/CreatedByFilter/CreatedByFilter.tsx
@@ -0,0 +1,28 @@
+/* eslint-disable react/prop-types */
+import { t } from "ttag";
+import type { SearchFilterDropdown } from "metabase/search/types";
+import { UserNameDisplay } from "metabase/search/components/UserNameDisplay";
+import {
+  SearchUserPicker,
+  SearchUserPickerContainer,
+} from "metabase/search/components/SearchUserPicker";
+import {
+  stringifyUserIdArray,
+  parseUserIdArray,
+} from "metabase/search/utils/user-search-params";
+
+export const CreatedByFilter: SearchFilterDropdown<"created_by"> = {
+  iconName: "person",
+  label: () => t`Creator`,
+  type: "dropdown",
+  DisplayComponent: ({ value: userIdList }) => (
+    <UserNameDisplay label={CreatedByFilter.label()} userIdList={userIdList} />
+  ),
+  ContentComponent: ({ value, onChange, width }) => (
+    <SearchUserPickerContainer w={width}>
+      <SearchUserPicker value={value} onChange={onChange} />
+    </SearchUserPickerContainer>
+  ),
+  fromUrl: parseUserIdArray,
+  toUrl: stringifyUserIdArray,
+};
diff --git a/frontend/src/metabase/search/components/filters/CreatedByFilter/index.ts b/frontend/src/metabase/search/components/filters/CreatedByFilter/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a1a624786a6d76d22020f1e21db89c03bfeb594
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/CreatedByFilter/index.ts
@@ -0,0 +1 @@
+export * from "./CreatedByFilter";
diff --git a/frontend/src/metabase/search/components/filters/LastEditedAtFilter.tsx b/frontend/src/metabase/search/components/filters/LastEditedAtFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..95c9ebe8b02bc9523c7ab1f6ff549b5f9b75050d
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/LastEditedAtFilter.tsx
@@ -0,0 +1,25 @@
+/* eslint-disable react/prop-types */
+import { t } from "ttag";
+import { SearchFilterDateDisplay } from "metabase/search/components/SearchFilterDateDisplay";
+import { SearchFilterDatePicker } from "metabase/search/components/SearchFilterDatePicker";
+import type { SearchFilterDropdown } from "metabase/search/types";
+import { Box } from "metabase/ui";
+
+export const LastEditedAtFilter: SearchFilterDropdown<"last_edited_at"> = {
+  iconName: "calendar",
+  label: () => t`Last edit date`,
+  type: "dropdown",
+  DisplayComponent: ({ value: dateString }) => (
+    <SearchFilterDateDisplay
+      label={LastEditedAtFilter.label()}
+      value={dateString}
+    />
+  ),
+  ContentComponent: ({ value, onChange, width }) => (
+    <Box miw={width}>
+      <SearchFilterDatePicker value={value} onChange={onChange} />
+    </Box>
+  ),
+  fromUrl: value => value,
+  toUrl: value => value,
+};
diff --git a/frontend/src/metabase/search/components/filters/LastEditedByFilter.tsx b/frontend/src/metabase/search/components/filters/LastEditedByFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b386a2173a707198eb6c645b1a35fce7b9ad79b4
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/LastEditedByFilter.tsx
@@ -0,0 +1,26 @@
+/* eslint-disable react/prop-types */
+import { t } from "ttag";
+import { SearchUserPickerContainer } from "metabase/search/components/SearchUserPicker/SearchUserPicker.styled";
+import type { SearchFilterDropdown } from "metabase/search/types";
+import { UserNameDisplay } from "metabase/search/components/UserNameDisplay/UserNameDisplay";
+import { SearchUserPicker } from "metabase/search/components/SearchUserPicker/SearchUserPicker";
+import { stringifyUserIdArray, parseUserIdArray } from "metabase/search/utils";
+
+export const LastEditedByFilter: SearchFilterDropdown<"last_edited_by"> = {
+  iconName: "person",
+  label: () => t`Last editor`,
+  type: "dropdown",
+  DisplayComponent: ({ value: userIdList }) => (
+    <UserNameDisplay
+      userIdList={userIdList}
+      label={LastEditedByFilter.label()}
+    />
+  ),
+  ContentComponent: ({ value, onChange, width }) => (
+    <SearchUserPickerContainer w={width}>
+      <SearchUserPicker value={value} onChange={onChange} />
+    </SearchUserPickerContainer>
+  ),
+  fromUrl: parseUserIdArray,
+  toUrl: stringifyUserIdArray,
+};
diff --git a/frontend/src/metabase/search/components/filters/NativeQueryFilter/NativeQueryFilter.tsx b/frontend/src/metabase/search/components/filters/NativeQueryFilter/NativeQueryFilter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2e71928ce3ab07748fec7eb60f9130867345cf59
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/NativeQueryFilter/NativeQueryFilter.tsx
@@ -0,0 +1,9 @@
+import { NativeQueryLabel } from "metabase/search/components/filters/NativeQueryFilter/NativeQueryLabel";
+import type { SearchFilterToggle } from "metabase/search/types";
+
+export const NativeQueryFilter: SearchFilterToggle = {
+  label: NativeQueryLabel,
+  type: "toggle",
+  fromUrl: value => value === "true",
+  toUrl: (value: boolean) => (value ? "true" : null),
+};
diff --git a/frontend/src/metabase/search/components/filters/NativeQueryFilter/NativeQueryLabel.tsx b/frontend/src/metabase/search/components/filters/NativeQueryFilter/NativeQueryLabel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b926a946d96ce6779cf9a410d43b4b5e987f3085
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/NativeQueryFilter/NativeQueryLabel.tsx
@@ -0,0 +1,13 @@
+import { t } from "ttag";
+import { useDatabaseListQuery } from "metabase/common/hooks";
+import { getHasNativeWrite } from "metabase/selectors/data";
+
+export const NativeQueryLabel = () => {
+  const { data: databases = [] } = useDatabaseListQuery();
+
+  const hasNativeWrite = getHasNativeWrite(databases);
+
+  const filterLabel = hasNativeWrite ? t`native` : `SQL`;
+
+  return `Search the contents of ${filterLabel} queries`;
+};
diff --git a/frontend/src/metabase/search/components/filters/NativeQueryFilter/index.ts b/frontend/src/metabase/search/components/filters/NativeQueryFilter/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0be06782943990849f8154d914d91f695cba463c
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/NativeQueryFilter/index.ts
@@ -0,0 +1 @@
+export { NativeQueryFilter } from "./NativeQueryFilter";
diff --git a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.tsx b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.tsx
index 5a2bbc0ef1664195c777f21be980993a7df01dcb..a5aeb1d91b8bcb8f688a45791c319d1d9ea77e38 100644
--- a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.tsx
+++ b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.tsx
@@ -1,11 +1,28 @@
 import { t } from "ttag";
-import type { SearchSidebarFilterComponent } from "metabase/search/types";
-import { TypeFilterContent } from "metabase/search/components/filters/TypeFilter/TypeFilterContent";
-import { TypeFilterDisplay } from "metabase/search/components/filters/TypeFilter/TypeFilterDisplay";
+import type {
+  SearchFilterComponent,
+  TypeFilterProps,
+} from "metabase/search/types";
+import {
+  filterEnabledSearchTypes,
+  isEnabledSearchModelType,
+} from "metabase/search/utils/enabled-search-type";
+import { TypeFilterContent } from "./TypeFilterContent";
+import { TypeFilterDisplay } from "./TypeFilterDisplay";
 
-export const TypeFilter: SearchSidebarFilterComponent<"type"> = {
+export const TypeFilter: SearchFilterComponent<"type"> = {
   iconName: "dashboard",
-  title: t`Content type`,
+  label: () => t`Content type`,
+  type: "dropdown",
   DisplayComponent: TypeFilterDisplay,
   ContentComponent: TypeFilterContent,
+  fromUrl: value => {
+    if (Array.isArray(value)) {
+      return filterEnabledSearchTypes(value);
+    }
+    return isEnabledSearchModelType(value) ? [value] : [];
+  },
+  toUrl: (value: TypeFilterProps | null) => {
+    return value && value.length > 0 ? value : null;
+  },
 };
diff --git a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.unit.spec.tsx b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..773671b7205c03790db9ab2226bc04f92012b009
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilter.unit.spec.tsx
@@ -0,0 +1,73 @@
+import { TypeFilter } from "metabase/search/components/filters/TypeFilter";
+import type { EnabledSearchModelType } from "metabase-types/api";
+
+const fromUrl = TypeFilter.fromUrl;
+const toUrl = TypeFilter.toUrl;
+
+describe("fromUrl", () => {
+  it("should return an array with a single valid type when the input exactly matches a type", () => {
+    const types = "collection";
+    const result = fromUrl(types);
+    expect(result).toEqual(["collection"]);
+  });
+
+  it("should return an empty array when the input is null or undefined", () => {
+    const nullType = null;
+    const nullResult = fromUrl(nullType);
+    expect(nullResult).toEqual([]);
+
+    const undefinedType = undefined;
+    const undefinedResult = fromUrl(undefinedType);
+    expect(undefinedResult).toEqual([]);
+  });
+
+  it("should return an empty array when the input is an invalid type", () => {
+    const types = "invalidType";
+    const result = fromUrl(types);
+    expect(result).toEqual([]);
+  });
+
+  it("should return an array of valid types when the input is an array of valid types", () => {
+    const types: string[] = ["collection", "dashboard"];
+    const result = fromUrl(types);
+    expect(result).toEqual(["collection", "dashboard"]);
+  });
+
+  it("should return an array of valid types when the input is an array with one valid type and one invalid type", () => {
+    const types = ["collection", "invalidType"];
+    const result = fromUrl(types);
+    expect(result).toEqual(["collection"]);
+  });
+
+  it("should return an empty array when the input is an empty array", () => {
+    const types: string[] = [];
+    const result = fromUrl(types);
+    expect(result).toEqual([]);
+  });
+});
+
+describe("toUrl", () => {
+  it("should convert an array of valid types to an array of valid types", () => {
+    const types: EnabledSearchModelType[] = ["collection", "dashboard"];
+    const result = toUrl(types);
+    expect(result).toEqual(["collection", "dashboard"]);
+  });
+
+  it("should return null when the input array is empty", () => {
+    const types: EnabledSearchModelType[] = [];
+    const result = toUrl(types);
+    expect(result).toBeNull();
+  });
+
+  it("should return null when the input is undefined", () => {
+    const types = undefined;
+    const result = toUrl(types);
+    expect(result).toBeNull();
+  });
+
+  it("should return an array with a single valid type when the input array has one valid type", () => {
+    const types: EnabledSearchModelType[] = ["collection"];
+    const result = toUrl(types);
+    expect(result).toEqual(["collection"]);
+  });
+});
diff --git a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.tsx b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.tsx
index bd19799f169a5dc228d6112fed74e3b4416cc0b5..b9cb853d36df10d494451ac0191eb76e86082285 100644
--- a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.tsx
+++ b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.tsx
@@ -1,42 +1,51 @@
 /* eslint-disable react/prop-types */
-import type { SearchSidebarFilterComponent } from "metabase/search/types";
+import { useState } from "react";
+import type { SearchFilterDropdown } from "metabase/search/types";
 import { useSearchListQuery } from "metabase/common/hooks";
-import { enabledSearchTypes } from "metabase/search/constants";
 import { Checkbox, Stack } from "metabase/ui";
-import LoadingSpinner from "metabase/components/LoadingSpinner";
-import type { EnabledSearchModelType } from "metabase-types/api";
 import { getTranslatedEntityName } from "metabase/common/utils/model-names";
+import type { EnabledSearchModelType } from "metabase-types/api";
+import { SearchFilterPopoverWrapper } from "metabase/search/components/SearchFilterPopoverWrapper";
+import { filterEnabledSearchTypes } from "metabase/search/utils";
 
 const EMPTY_SEARCH_QUERY = { models: "dataset", limit: 1 } as const;
-export const TypeFilterContent: SearchSidebarFilterComponent<"type">["ContentComponent"] =
-  ({ value, onChange }) => {
+export const TypeFilterContent: SearchFilterDropdown<"type">["ContentComponent"] =
+  ({ value, onChange, width }) => {
     const { metadata, isLoading } = useSearchListQuery({
       query: EMPTY_SEARCH_QUERY,
     });
 
+    const [selectedTypes, setSelectedTypes] = useState<
+      EnabledSearchModelType[]
+    >(value ?? []);
+
     const availableModels = (metadata && metadata.available_models) ?? [];
-    const typeFilters: EnabledSearchModelType[] = enabledSearchTypes.filter(
-      model => availableModels.includes(model),
-    );
+    const typeFilters = filterEnabledSearchTypes(availableModels);
 
-    return isLoading ? (
-      <LoadingSpinner />
-    ) : (
-      <Checkbox.Group
-        data-testid="type-filter-checkbox-group"
-        w="100%"
-        value={value}
-        onChange={onChange}
+    return (
+      <SearchFilterPopoverWrapper
+        isLoading={isLoading}
+        onApply={() => onChange(selectedTypes)}
+        w={width}
       >
-        <Stack spacing="md" justify="center" align="flex-start">
-          {typeFilters.map(model => (
-            <Checkbox
-              key={model}
-              value={model}
-              label={getTranslatedEntityName(model)}
-            />
-          ))}
-        </Stack>
-      </Checkbox.Group>
+        <Checkbox.Group
+          data-testid="type-filter-checkbox-group"
+          w="100%"
+          value={selectedTypes}
+          onChange={value =>
+            setSelectedTypes(value as EnabledSearchModelType[])
+          }
+        >
+          <Stack spacing="md" p="md" justify="center" align="flex-start">
+            {typeFilters.map(model => (
+              <Checkbox
+                key={model}
+                value={model}
+                label={getTranslatedEntityName(model)}
+              />
+            ))}
+          </Stack>
+        </Checkbox.Group>
+      </SearchFilterPopoverWrapper>
     );
   };
diff --git a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.unit.spec.tsx b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.unit.spec.tsx
index ddc225845440bf568b7dc9463cde3926c57b28bd..cecc114d14501db333769e83d638ce0ac9781b98 100644
--- a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.unit.spec.tsx
+++ b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterContent.unit.spec.tsx
@@ -146,10 +146,10 @@ describe("TypeFilterContent", () => {
 
     for (let i = 0; i < options.length; i++) {
       userEvent.click(options[i]);
-      expect(onChangeFilters).toHaveReturnedTimes(i + 1);
     }
 
-    expect(onChangeFilters).toHaveReturnedTimes(TEST_TYPES.length);
+    userEvent.click(screen.getByText("Apply"));
+    expect(onChangeFilters).toHaveReturnedTimes(1);
     expect(onChangeFilters).toHaveBeenLastCalledWith(TEST_TYPES);
   });
 
@@ -161,8 +161,8 @@ describe("TypeFilterContent", () => {
     for (const checkedOption of checkedOptions) {
       userEvent.click(checkedOption);
     }
-
-    expect(onChangeFilters).toHaveReturnedTimes(TEST_TYPE_SUBSET.length);
+    userEvent.click(screen.getByText("Apply"));
+    expect(onChangeFilters).toHaveReturnedTimes(1);
     expect(onChangeFilters).toHaveBeenLastCalledWith([]);
   });
 });
diff --git a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterDisplay.tsx b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterDisplay.tsx
index 5de7b75cb6426afee6394d1d34a7c261788b89db..4915f2cb2b2f464eaef3579e797f94fed69fca0e 100644
--- a/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterDisplay.tsx
+++ b/frontend/src/metabase/search/components/filters/TypeFilter/TypeFilterDisplay.tsx
@@ -1,22 +1,22 @@
 /* eslint-disable react/prop-types */
 import { t } from "ttag";
-import type { SearchSidebarFilterComponent } from "metabase/search/types";
 import { Text } from "metabase/ui";
-import { TypeFilter } from "metabase/search/components/filters/TypeFilter/TypeFilter";
+import { TypeFilter } from "metabase/search/components/filters/TypeFilter";
+import type { SearchFilterDropdown } from "metabase/search/types";
 import { getTranslatedEntityName } from "metabase/common/utils/model-names";
 
-export const TypeFilterDisplay: SearchSidebarFilterComponent<"type">["DisplayComponent"] =
+export const TypeFilterDisplay: SearchFilterDropdown<"type">["DisplayComponent"] =
   ({ value }) => {
     let titleText = "";
     if (!value || !value.length) {
-      titleText = TypeFilter.title;
+      titleText = TypeFilter.label();
     } else if (value.length === 1) {
       titleText = getTranslatedEntityName(value[0]) ?? t`1 type selected`;
     } else {
       titleText = value.length + t` types selected`;
     }
     return (
-      <Text c="inherit" weight={700}>
+      <Text c="inherit" weight={700} truncate>
         {titleText}
       </Text>
     );
diff --git a/frontend/src/metabase/search/components/filters/TypeFilter/index.ts b/frontend/src/metabase/search/components/filters/TypeFilter/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b12c3c3bc4342ba538334697951588b3393ab181
--- /dev/null
+++ b/frontend/src/metabase/search/components/filters/TypeFilter/index.ts
@@ -0,0 +1 @@
+export * from "./TypeFilter";
diff --git a/frontend/src/metabase/search/constants.ts b/frontend/src/metabase/search/constants.ts
index 2c3e531042faabf46a7240ffb14c49115cf22042..8878231806a6447830c7b9fbbd09e44e267dad27 100644
--- a/frontend/src/metabase/search/constants.ts
+++ b/frontend/src/metabase/search/constants.ts
@@ -2,6 +2,12 @@ import type { EnabledSearchModelType } from "metabase-types/api";
 
 export const SearchFilterKeys = {
   Type: "type",
+  Verified: "verified",
+  CreatedBy: "created_by",
+  CreatedAt: "created_at",
+  LastEditedBy: "last_edited_by",
+  LastEditedAt: "last_edited_at",
+  NativeQuery: "search_native_query",
 } as const;
 
 export const enabledSearchTypes: EnabledSearchModelType[] = [
diff --git a/frontend/src/metabase/search/containers/SearchApp.jsx b/frontend/src/metabase/search/containers/SearchApp.jsx
index 1f5564b7abc7544090856ccea29c65df1a683961..d669af649bb929a042d7a70b406b6246acdd11d9 100644
--- a/frontend/src/metabase/search/containers/SearchApp.jsx
+++ b/frontend/src/metabase/search/containers/SearchApp.jsx
@@ -19,7 +19,7 @@ import {
 } from "metabase/search/utils";
 import { PAGE_SIZE } from "metabase/search/containers/constants";
 import { SearchFilterKeys } from "metabase/search/constants";
-import { SearchSidebar } from "metabase/search/components/SearchSidebar/SearchSidebar";
+import { SearchSidebar } from "metabase/search/components/SearchSidebar";
 import { useDispatch } from "metabase/lib/redux";
 import {
   SearchControls,
@@ -78,17 +78,14 @@ function SearchApp({ location }) {
       <Text size="xl" weight={700}>
         {jt`Results for "${searchText}"`}
       </Text>
-      <Search.ListLoader query={query} wrapped>
-        {({ list, metadata }) => (
-          <SearchBody direction="column" justify="center">
-            <SearchControls>
-              <SearchSidebar
-                value={searchFilters}
-                onChangeFilters={onFilterChange}
-              />
-            </SearchControls>
-            <SearchResultContainer>
-              {list.length === 0 ? (
+      <SearchBody direction="column" justify="center">
+        <SearchControls pb="lg">
+          <SearchSidebar value={searchFilters} onChange={onFilterChange} />
+        </SearchControls>
+        <SearchResultContainer>
+          <Search.ListLoader query={query} wrapped>
+            {({ list, metadata }) =>
+              list.length === 0 ? (
                 <Paper shadow="lg" p="2rem">
                   <EmptyState
                     title={t`Didn't find anything`}
@@ -102,7 +99,10 @@ function SearchApp({ location }) {
                 </Paper>
               ) : (
                 <Box>
-                  <SearchResultSection items={list} />
+                  <SearchResultSection
+                    totalResults={metadata.total}
+                    results={list}
+                  />
                   <Group justify="flex-end" align="center" my="1rem">
                     <PaginationControls
                       showTotal
@@ -115,11 +115,11 @@ function SearchApp({ location }) {
                     />
                   </Group>
                 </Box>
-              )}
-            </SearchResultContainer>
-          </SearchBody>
-        )}
-      </Search.ListLoader>
+              )
+            }
+          </Search.ListLoader>
+        </SearchResultContainer>
+      </SearchBody>
     </SearchMain>
   );
 }
diff --git a/frontend/src/metabase/search/containers/SearchApp.styled.tsx b/frontend/src/metabase/search/containers/SearchApp.styled.tsx
index 071dd34fb7e8a6c6835e4f9f47dd7fd08b005b6b..8866421d91ff5851498af5aa90a30e2d314fa6da 100644
--- a/frontend/src/metabase/search/containers/SearchApp.styled.tsx
+++ b/frontend/src/metabase/search/containers/SearchApp.styled.tsx
@@ -3,10 +3,10 @@ import {
   breakpointMinMedium,
   breakpointMinSmall,
 } from "metabase/styled-components/theme";
-import { Flex } from "metabase/ui";
+import { Flex, Stack } from "metabase/ui";
 
 const SEARCH_BODY_WIDTH = "90rem";
-const SEARCH_SIDEBAR_WIDTH = "15rem";
+const SEARCH_SIDEBAR_WIDTH = "18rem";
 
 export const SearchMain = styled(Flex)`
   width: min(calc(${SEARCH_BODY_WIDTH} + ${SEARCH_SIDEBAR_WIDTH}), 100%);
@@ -23,7 +23,9 @@ export const SearchBody = styled(Flex)`
   }
 `;
 
-export const SearchControls = styled.div`
+export const SearchControls = styled(Stack)`
+  overflow: hidden;
+
   ${breakpointMinMedium} {
     flex: 0 0 ${SEARCH_SIDEBAR_WIDTH};
   }
diff --git a/frontend/src/metabase/search/containers/SearchApp.unit.spec.tsx b/frontend/src/metabase/search/containers/SearchApp.unit.spec.tsx
index 02439416ef4c4706b8fa352d77389fbcd47ed6ec..e2c4cadd0922953e692a1aabc59790236b5e2491 100644
--- a/frontend/src/metabase/search/containers/SearchApp.unit.spec.tsx
+++ b/frontend/src/metabase/search/containers/SearchApp.unit.spec.tsx
@@ -8,14 +8,18 @@ import {
 import SearchApp from "metabase/search/containers/SearchApp";
 import { Route } from "metabase/hoc/Title";
 import {
+  setupCollectionByIdEndpoint,
   setupDatabasesEndpoints,
   setupSearchEndpoints,
   setupTableEndpoints,
+  setupUsersEndpoints,
 } from "__support__/server-mocks";
 import {
+  createMockCollection,
   createMockDatabase,
   createMockSearchResult,
   createMockTable,
+  createMockUserListResult,
 } from "metabase-types/api/mocks";
 import type { EnabledSearchModelType, SearchResult } from "metabase-types/api";
 
@@ -53,6 +57,8 @@ const TEST_SEARCH_RESULTS: SearchResult[] = TEST_ITEMS.map((metadata, index) =>
 
 const TEST_DATABASE = createMockDatabase();
 const TEST_TABLE = createMockTable();
+const TEST_USER_LIST = [createMockUserListResult()];
+const TEST_COLLECTION = createMockCollection();
 
 const setup = async ({
   searchText,
@@ -66,6 +72,10 @@ const setup = async ({
   setupDatabasesEndpoints([TEST_DATABASE]);
   setupSearchEndpoints(searchItems);
   setupTableEndpoints(TEST_TABLE);
+  setupUsersEndpoints(TEST_USER_LIST);
+  setupCollectionByIdEndpoint({
+    collections: [TEST_COLLECTION],
+  });
 
   // for testing the hydration of search text and filters on page load
   const params = {
@@ -158,7 +168,12 @@ describe("SearchApp", () => {
           searchText: "Test",
         });
 
-        userEvent.click(screen.getByTestId("sidebar-filter-dropdown-button"));
+        userEvent.click(
+          within(screen.getByTestId("type-search-filter")).getByTestId(
+            "sidebar-filter-dropdown-button",
+          ),
+        );
+
         await waitForLoaderToBeRemoved();
 
         const popover = within(screen.getByTestId("popover"));
@@ -169,7 +184,7 @@ describe("SearchApp", () => {
             ] as EnabledSearchModelType,
           }),
         );
-        userEvent.click(popover.getByRole("button", { name: "Apply filters" }));
+        userEvent.click(popover.getByRole("button", { name: "Apply" }));
 
         const url = history.getCurrentLocation();
         expect(url.query.type).toEqual(model);
@@ -194,17 +209,12 @@ describe("SearchApp", () => {
           name,
         );
 
-        const fieldSetContent = within(screen.getByTestId("field-set-content"));
-
-        expect(
-          fieldSetContent.getByText(
-            TYPE_FILTER_LABELS[model as EnabledSearchModelType],
-          ),
-        ).toBeInTheDocument();
+        const typeFilter = within(screen.getByTestId("type-search-filter"));
+        const fieldSetContent = typeFilter.getByTestId("field-set-content");
 
-        expect(
-          fieldSetContent.getByLabelText("close icon"),
-        ).toBeInTheDocument();
+        expect(fieldSetContent).toHaveTextContent(
+          TYPE_FILTER_LABELS[model as EnabledSearchModelType],
+        );
       },
     );
   });
diff --git a/frontend/src/metabase/search/containers/SearchResultSection.tsx b/frontend/src/metabase/search/containers/SearchResultSection.tsx
index db5fda7bc4ed365c568c72829f0d5ecc53b1cf09..ea8cc2d8da3def4a4307d37f0b29b69940c9a10c 100644
--- a/frontend/src/metabase/search/containers/SearchResultSection.tsx
+++ b/frontend/src/metabase/search/containers/SearchResultSection.tsx
@@ -1,11 +1,33 @@
+import { msgid, ngettext } from "ttag";
 import type { WrappedResult } from "metabase/search/types";
-import Card from "metabase/components/Card";
 import { SearchResult } from "metabase/search/components/SearchResult";
+import { Paper, Stack, Text } from "metabase/ui";
 
-export const SearchResultSection = ({ items }: { items: WrappedResult[] }) => (
-  <Card className="pt2">
-    {items.map(item => {
-      return <SearchResult key={`${item.id}__${item.model}`} result={item} />;
-    })}
-  </Card>
-);
+export const SearchResultSection = ({
+  results,
+  totalResults,
+}: {
+  results: WrappedResult[];
+  totalResults: number;
+}) => {
+  const resultsLabel = ngettext(
+    msgid`${totalResults} result`,
+    `${totalResults} results`,
+    totalResults,
+  );
+
+  return (
+    <Paper px="sm" py="md">
+      <Stack spacing="sm">
+        <Text tt="uppercase" fw={700} ml="sm" mb="sm">
+          {resultsLabel}
+        </Text>
+        {results.map(item => {
+          return (
+            <SearchResult key={`${item.id}__${item.model}`} result={item} />
+          );
+        })}
+      </Stack>
+    </Paper>
+  );
+};
diff --git a/frontend/src/metabase/search/types.ts b/frontend/src/metabase/search/types.ts
index 1dcfaa1c775399b226bfa25d7e1b3aa427fc6575..358bd0cc2a4314002f3e28cb1cf7f20b15f04463 100644
--- a/frontend/src/metabase/search/types.ts
+++ b/frontend/src/metabase/search/types.ts
@@ -2,9 +2,9 @@ import type { Location } from "history";
 import type { ComponentType } from "react";
 
 import type {
-  Collection,
   EnabledSearchModelType,
   SearchResult,
+  UserId,
 } from "metabase-types/api";
 import type { IconName } from "metabase/core/components/Icon";
 import type { SearchFilterKeys } from "metabase/search/constants";
@@ -17,30 +17,73 @@ export interface WrappedResult extends SearchResult {
     width?: number;
     height?: number;
   };
-  getCollection: () => Partial<Collection>;
+  getCollection: () => SearchResult["collection"];
 }
 
 export type TypeFilterProps = EnabledSearchModelType[];
+export type CreatedByFilterProps = UserId[];
+export type CreatedAtFilterProps = string | null;
+export type LastEditedByProps = UserId[];
+export type LastEditedAtFilterProps = string | null;
+export type VerifiedFilterProps = true | null;
+export type NativeQueryFilterProps = true | null;
 
 export type SearchFilterPropTypes = {
   [SearchFilterKeys.Type]: TypeFilterProps;
+  [SearchFilterKeys.Verified]: VerifiedFilterProps;
+  [SearchFilterKeys.CreatedBy]: CreatedByFilterProps;
+  [SearchFilterKeys.CreatedAt]: CreatedAtFilterProps;
+  [SearchFilterKeys.LastEditedBy]: LastEditedByProps;
+  [SearchFilterKeys.LastEditedAt]: LastEditedAtFilterProps;
+  [SearchFilterKeys.NativeQuery]: NativeQueryFilterProps;
 };
 
 export type FilterTypeKeys = keyof SearchFilterPropTypes;
 
+// All URL query parameters are returned as strings so we need to account
+// for that when parsing them to our filter components
+export type SearchQueryParamValue = string | string[] | null | undefined;
+export type URLSearchFilterQueryParams = Partial<
+  Record<FilterTypeKeys, SearchQueryParamValue>
+>;
+export type SearchAwareLocation = Location<
+  { q?: string } & URLSearchFilterQueryParams
+>;
+
 export type SearchFilters = Partial<SearchFilterPropTypes>;
 
 export type SearchFilterComponentProps<T extends FilterTypeKeys = any> = {
-  value?: SearchFilterPropTypes[T];
+  value: SearchFilterPropTypes[T];
   onChange: (value: SearchFilterPropTypes[T]) => void;
   "data-testid"?: string;
+  width?: string;
 } & Record<string, unknown>;
 
-export type SearchAwareLocation = Location<{ q?: string } & SearchFilters>;
+type SidebarFilterType = "dropdown" | "toggle";
+
+interface SearchFilter<T extends FilterTypeKeys = any> {
+  type: SidebarFilterType;
+  label: () => string;
+  iconName?: IconName;
+
+  // parses the string value of a URL query parameter to the filter value
+  fromUrl: (value: SearchQueryParamValue) => SearchFilterPropTypes[T];
+
+  // converts filter value to URL query parameter string value
+  toUrl: (value: SearchFilterPropTypes[T] | null) => SearchQueryParamValue;
+}
 
-export type SearchSidebarFilterComponent<T extends FilterTypeKeys = any> = {
-  title: string;
-  iconName: IconName;
+export interface SearchFilterDropdown<T extends FilterTypeKeys = any>
+  extends SearchFilter {
+  type: "dropdown";
   DisplayComponent: ComponentType<Pick<SearchFilterComponentProps<T>, "value">>;
   ContentComponent: ComponentType<SearchFilterComponentProps<T>>;
-};
+}
+
+export interface SearchFilterToggle extends SearchFilter {
+  type: "toggle";
+}
+
+export type SearchFilterComponent<T extends FilterTypeKeys = any> =
+  | SearchFilterDropdown<T>
+  | SearchFilterToggle;
diff --git a/frontend/src/metabase/search/utils/enabled-search-type/enabled-search-type.ts b/frontend/src/metabase/search/utils/enabled-search-type/enabled-search-type.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4fe1fd849decb266bfffbba4b744011e400c7fc8
--- /dev/null
+++ b/frontend/src/metabase/search/utils/enabled-search-type/enabled-search-type.ts
@@ -0,0 +1,16 @@
+import type { EnabledSearchModelType } from "metabase-types/api";
+import { enabledSearchTypes } from "metabase/search/constants";
+
+export function isEnabledSearchModelType(
+  value: unknown,
+): value is EnabledSearchModelType {
+  return (
+    typeof value === "string" && enabledSearchTypes.some(type => type === value)
+  );
+}
+
+export const filterEnabledSearchTypes = (
+  values: unknown[],
+): EnabledSearchModelType[] => {
+  return values.filter(isEnabledSearchModelType);
+};
diff --git a/frontend/src/metabase/search/utils/enabled-search-type/enabled-search-type.unit.spec.ts b/frontend/src/metabase/search/utils/enabled-search-type/enabled-search-type.unit.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a962a274725a6a72599986cc1fa839ffcb8451e
--- /dev/null
+++ b/frontend/src/metabase/search/utils/enabled-search-type/enabled-search-type.unit.spec.ts
@@ -0,0 +1,50 @@
+import {
+  filterEnabledSearchTypes,
+  isEnabledSearchModelType,
+} from "metabase/search/utils";
+
+const TEST_VALID_VALUES = [
+  "collection",
+  "dashboard",
+  "card",
+  "database",
+  "table",
+  "dataset",
+  "action",
+];
+
+const TEST_INVALID_VALUES = [null, undefined, 123, "invalid", [], {}];
+
+describe("isEnabledSearchModelType", () => {
+  it("should return true if value is in EnabledSearchModelType", () => {
+    TEST_VALID_VALUES.forEach(value => {
+      expect(isEnabledSearchModelType(value)).toBe(true);
+    });
+  });
+
+  it("should return false if value is not in EnabledSearchModelType", () => {
+    TEST_INVALID_VALUES.forEach(value => {
+      expect(isEnabledSearchModelType(value)).toBe(false);
+    });
+  });
+});
+
+describe("filterEnabledSearchTypes", () => {
+  it("should filter and return valid EnabledSearchModelTypes", () => {
+    const inputValues = [...TEST_VALID_VALUES, ...TEST_INVALID_VALUES];
+
+    const filteredValues = filterEnabledSearchTypes(inputValues);
+    expect(filteredValues).toEqual(TEST_VALID_VALUES);
+  });
+
+  it("should return an empty array if no EnabledSearchModelType values found", () => {
+    const filteredValues = filterEnabledSearchTypes(TEST_INVALID_VALUES);
+    expect(filteredValues).toEqual([]);
+  });
+
+  it("should return an empty array when an empty input array is provided", () => {
+    const inputValues: unknown[] = [];
+    const filteredValues = filterEnabledSearchTypes(inputValues);
+    expect(filteredValues).toEqual([]);
+  });
+});
diff --git a/frontend/src/metabase/search/utils/enabled-search-type/index.ts b/frontend/src/metabase/search/utils/enabled-search-type/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b23246ec6d5adfb1756c0f19953588dfee73f298
--- /dev/null
+++ b/frontend/src/metabase/search/utils/enabled-search-type/index.ts
@@ -0,0 +1,4 @@
+export {
+  isEnabledSearchModelType,
+  filterEnabledSearchTypes,
+} from "./enabled-search-type";
diff --git a/frontend/src/metabase/search/utils/index.ts b/frontend/src/metabase/search/utils/index.ts
index 4871119ecd38ae8baed4e317987a52ad62cd7c57..032853aba4e747c46a27c5990ffc62a2fe1f6a62 100644
--- a/frontend/src/metabase/search/utils/index.ts
+++ b/frontend/src/metabase/search/utils/index.ts
@@ -1 +1,3 @@
 export * from "./search-location";
+export * from "./user-search-params";
+export * from "./enabled-search-type";
diff --git a/frontend/src/metabase/search/utils/search-location/search-location.ts b/frontend/src/metabase/search/utils/search-location/search-location.ts
index 311ee8586119e567fba65ba853ba707f99446161..c879714ae288a4f47a6542ac07c7d21b21b09a85 100644
--- a/frontend/src/metabase/search/utils/search-location/search-location.ts
+++ b/frontend/src/metabase/search/utils/search-location/search-location.ts
@@ -1,11 +1,13 @@
 import _ from "underscore";
 
-import type { SearchAwareLocation, SearchFilters } from "metabase/search/types";
+import type {
+  SearchAwareLocation,
+  URLSearchFilterQueryParams,
+} from "metabase/search/types";
 import { SearchFilterKeys } from "metabase/search/constants";
 
-export function isSearchPageLocation(location: SearchAwareLocation): boolean {
-  const components = location.pathname.split("/");
-  return components[components.length - 1] === "search";
+export function isSearchPageLocation(location?: SearchAwareLocation): boolean {
+  return location ? /^\/?search$/.test(location.pathname) : false;
 }
 
 export function getSearchTextFromLocation(
@@ -19,7 +21,7 @@ export function getSearchTextFromLocation(
 
 export function getFiltersFromLocation(
   location: SearchAwareLocation,
-): SearchFilters {
+): URLSearchFilterQueryParams {
   if (isSearchPageLocation(location)) {
     return _.pick(location.query, Object.values(SearchFilterKeys));
   }
diff --git a/frontend/src/metabase/search/utils/search-location/search-location.unit.spec.ts b/frontend/src/metabase/search/utils/search-location/search-location.unit.spec.ts
index f7a40542c7bb209307d6a4590b1a3f57140f23b8..d3ca676499b6604518a3952edc1afb492308dbb2 100644
--- a/frontend/src/metabase/search/utils/search-location/search-location.unit.spec.ts
+++ b/frontend/src/metabase/search/utils/search-location/search-location.unit.spec.ts
@@ -8,20 +8,28 @@ import type { SearchAwareLocation } from "metabase/search/types";
 import { SearchFilterKeys } from "metabase/search/constants";
 
 describe("isSearchPageLocation", () => {
-  it('should return true when the last component of pathname is "search"', () => {
-    const location = {
-      pathname: "/search",
-      query: {},
-    };
-    expect(isSearchPageLocation(location as SearchAwareLocation)).toBe(true);
+  it("should return true for a search page location", () => {
+    const location = { pathname: "/search" };
+    const result = isSearchPageLocation(location as SearchAwareLocation);
+    expect(result).toBe(true);
   });
 
-  it('should return false when the last component of pathname is not "search"', () => {
-    const location = {
-      pathname: "/collection/root",
-      query: {},
-    };
-    expect(isSearchPageLocation(location as SearchAwareLocation)).toBe(false);
+  it("should return true for a search page location with query params", () => {
+    const location = { pathname: "/search", search: "?q=test" };
+    const result = isSearchPageLocation(location as SearchAwareLocation);
+    expect(result).toBe(true);
+  });
+
+  it('should return false for non-search location that might have "search" in the path', () => {
+    const location = { pathname: "/collection/1-search" };
+    const result = isSearchPageLocation(location as SearchAwareLocation);
+    expect(result).toBe(false);
+  });
+
+  it("should return false for non-search location", () => {
+    const location = { pathname: "/some-page" };
+    const result = isSearchPageLocation(location as SearchAwareLocation);
+    expect(result).toBe(false);
   });
 });
 
diff --git a/frontend/src/metabase/search/utils/user-search-params/index.ts b/frontend/src/metabase/search/utils/user-search-params/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a619b76eae86d9812ad8a1f15c7dbabf42b8e41
--- /dev/null
+++ b/frontend/src/metabase/search/utils/user-search-params/index.ts
@@ -0,0 +1 @@
+export { parseUserIdArray, stringifyUserIdArray } from "./user-search-params";
diff --git a/frontend/src/metabase/search/utils/user-search-params/user-search-params.ts b/frontend/src/metabase/search/utils/user-search-params/user-search-params.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb19a9d37ce3d829509ce9293f9cc5baed60606b
--- /dev/null
+++ b/frontend/src/metabase/search/utils/user-search-params/user-search-params.ts
@@ -0,0 +1,41 @@
+import type { UserId } from "metabase-types/api";
+import type { SearchQueryParamValue } from "metabase/search/types";
+import { isNotNull } from "metabase/core/utils/types";
+
+export const parseUserIdArray = (value: SearchQueryParamValue): UserId[] => {
+  if (!value) {
+    return [];
+  }
+
+  if (typeof value === "string") {
+    const parsedValue = parseUserId(value);
+    return parsedValue ? [parsedValue] : [];
+  }
+
+  if (Array.isArray(value)) {
+    const parsedIds: (number | null)[] = value.map(idString =>
+      parseUserId(idString),
+    );
+    return parsedIds.filter(isNotNull);
+  }
+
+  return [];
+};
+
+export const parseUserId = (value: SearchQueryParamValue): UserId | null => {
+  if (!value || Array.isArray(value)) {
+    return null;
+  }
+  const numValue = Number(value);
+
+  if (!numValue || isNaN(numValue) || numValue <= 0) {
+    return null;
+  }
+
+  return numValue;
+};
+
+export const stringifyUserIdArray = (
+  value?: UserId[] | null,
+): SearchQueryParamValue =>
+  value ? value.map(idString => String(idString)) : [];
diff --git a/frontend/src/metabase/search/utils/user-search-params/user-search-params.unit.spec.ts b/frontend/src/metabase/search/utils/user-search-params/user-search-params.unit.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8c0a1a564d37261e827c220fec6866b1f38e8f26
--- /dev/null
+++ b/frontend/src/metabase/search/utils/user-search-params/user-search-params.unit.spec.ts
@@ -0,0 +1,104 @@
+import {
+  parseUserId,
+  stringifyUserIdArray,
+  parseUserIdArray,
+} from "./user-search-params";
+
+describe("parseUserIdArray", () => {
+  it("should return a UserId array when value is a string", () => {
+    const userId = "123";
+    const result = parseUserIdArray(userId);
+    expect(result).toStrictEqual([123]);
+  });
+
+  it("should return a UserId array when value is an array of strings", () => {
+    const userId = ["123", "456"];
+    const result = parseUserIdArray(userId);
+    expect(result).toStrictEqual([123, 456]);
+  });
+
+  it("should filter invalid values from an array of strings", () => {
+    const userId = ["123", "abc", "456", "def", "789", ""];
+    const result = parseUserIdArray(userId);
+    expect(result).toStrictEqual([123, 456, 789]);
+  });
+
+  it("should return an empty array when value is null or undefined", () => {
+    const nullResult = parseUserIdArray(null);
+    expect(nullResult).toStrictEqual([]);
+
+    const undefinedResult = parseUserIdArray(undefined);
+    expect(undefinedResult).toStrictEqual([]);
+  });
+
+  it("should return an empty array when value is an empty array", () => {
+    const userId: string[] = [];
+    const result = parseUserIdArray(userId);
+    expect(result).toStrictEqual([]);
+  });
+});
+
+describe("parseUserId", () => {
+  it("should convert a valid string to a number", () => {
+    const userId = "123";
+    const result = parseUserId(userId);
+    expect(result).toBe(123);
+  });
+
+  it("should return null when userId is null or undefined", () => {
+    const nullResult = parseUserId(null);
+    expect(nullResult).toBeNull();
+
+    const undefinedResult = parseUserId(undefined);
+    expect(undefinedResult).toBeNull();
+  });
+
+  it("should return null when userId is 0", () => {
+    const userId = "0";
+    const result = parseUserId(userId);
+    expect(result).toBeNull();
+  });
+
+  it("should return null when userId is a negative number", () => {
+    const userId = "-1";
+    const result = parseUserId(userId);
+    expect(result).toBeNull();
+  });
+
+  it("should return null when userId is a string that cannot be converted to a number", () => {
+    const userId = "abc";
+    const result = parseUserId(userId);
+    expect(result).toBeNull();
+  });
+
+  it("should return null when userId is an empty string", () => {
+    const userId = "";
+    const result = parseUserId(userId);
+    expect(result).toBeNull();
+  });
+
+  it("should return null when userId is an array", () => {
+    const userId = ["123"];
+    const result = parseUserId(userId);
+    expect(result).toBeNull();
+  });
+});
+
+describe("stringifyUserIdArray", () => {
+  it("should convert an UserId number array to a string", () => {
+    const userId = [1, 2, 3, 4];
+    const result = stringifyUserIdArray(userId);
+    expect(result).toStrictEqual(["1", "2", "3", "4"]);
+  });
+
+  it("should convert an UserId number to a string", () => {
+    const userId = [1];
+    const result = stringifyUserIdArray(userId);
+    expect(result).toStrictEqual(["1"]);
+  });
+
+  it("should return null if the input is null", () => {
+    const result = stringifyUserIdArray([]);
+    expect(result).toStrictEqual([]);
+  });
+});
diff --git a/frontend/src/metabase/visualizations/visualizations/LinkViz/LinkViz.unit.spec.tsx b/frontend/src/metabase/visualizations/visualizations/LinkViz/LinkViz.unit.spec.tsx
index 5426707674d1dce8ec26ce3ef49d7cadac603f48..70a947a0e564c43f885a9dcfc2d6930708043500 100644
--- a/frontend/src/metabase/visualizations/visualizations/LinkViz/LinkViz.unit.spec.tsx
+++ b/frontend/src/metabase/visualizations/visualizations/LinkViz/LinkViz.unit.spec.tsx
@@ -10,6 +10,8 @@ import {
 import {
   setupSearchEndpoints,
   setupRecentViewsEndpoints,
+  setupUsersEndpoints,
+  setupCollectionByIdEndpoint,
 } from "__support__/server-mocks";
 import * as domUtils from "metabase/lib/dom";
 import registerVisualizations from "metabase/visualizations/register";
@@ -22,6 +24,7 @@ import {
   createMockRecentItem,
   createMockTable,
   createMockDashboard,
+  createMockUser,
 } from "metabase-types/api/mocks";
 
 import type { LinkVizProps } from "./LinkViz";
@@ -111,12 +114,13 @@ const searchingDashcard = createMockDashboardCardWithVirtualCard({
   },
 });
 
+const searchCardCollection = createMockCollection();
 const searchCardItem = createMockCollectionItem({
   id: 1,
   model: "card",
   name: "Question Uno",
   display: "pie",
-  collection: createMockCollection(),
+  collection: searchCardCollection,
 });
 
 const setup = (options?: Partial<LinkVizProps>) => {
@@ -241,6 +245,10 @@ describe("LinkViz", () => {
 
     it("clicking a search item should update the entity", async () => {
       setupSearchEndpoints([searchCardItem]);
+      setupUsersEndpoints([createMockUser()]);
+      setupCollectionByIdEndpoint({
+        collections: [searchCardCollection],
+      });
 
       const { changeSpy } = setup({
         isEditing: true,
diff --git a/resources/migrations/000_migrations.yaml b/resources/migrations/000_migrations.yaml
index 21503e18bd3a728fd69762c7b8b72ac4fc231afa..c4265ddd2aa32b64c5f49060cedae1acc47dd8bb 100644
--- a/resources/migrations/000_migrations.yaml
+++ b/resources/migrations/000_migrations.yaml
@@ -15173,6 +15173,52 @@ databaseChangeLog:
               column:
                 name: action_id
 
+  - changeSet:
+      id: v48.00-007
+      author: qnkhuat
+      comment: Added 0.48.0 - Add revision.most_recent
+      changes:
+        - addColumn:
+            tableName: revision
+            columns:
+              - column:
+                  name: most_recent
+                  type: boolean
+                  defaultValueBoolean: false
+                  remarks: 'Whether a revision is the most recent one'
+                  constraints:
+                    nullable: false
+
+  - changeSet:
+      id: v48.00-008
+      author: qnkhuat
+      comment: Set revision.most_recent = true for latest revisions
+      changes:
+        - sql:
+            dbms: postgresql,h2
+            sql: >-
+              UPDATE revision r
+              SET most_recent = true
+              WHERE (model, model_id, timestamp) IN (
+                                 SELECT model, model_id, MAX(timestamp)
+                                 FROM revision
+                                 GROUP BY model, model_id);
+          # mysql and mariadb does not allow update on a table that is in the select part
+          # so it we join for them
+        - sql:
+            dbms: mysql,mariadb
+            sql: >-
+              UPDATE revision r
+              JOIN (
+                  SELECT model, model_id, MAX(timestamp) AS max_timestamp
+                  FROM revision
+                  GROUP BY model, model_id
+              ) AS subquery
+              ON r.model = subquery.model AND r.model_id = subquery.model_id AND r.timestamp = subquery.max_timestamp
+              SET r.most_recent = true;
+      rollback: # nothing to do since the most_recent will be dropped anyway
+
+
   - changeSet:
       id: v48.00-009
       author: calherries
@@ -15229,6 +15275,33 @@ databaseChangeLog:
                   columnNames: table_id, role
                   name: uq_table_privileges_table_id_role
 
+
+  - changeSet:
+      id: v48.00-010
+      author: qnkhuat
+      comment: Remove ON UPDATE for revision.timestamp on mysql, mariadb
+      preConditions:
+          # If preconditions fail (i.e., dbms is not mysql or mariadb) then mark this migration as 'ran'
+          - onFail: MARK_RAN
+          - dbms:
+              type: mysql,mariadb
+      changes:
+          - sql:
+                sql: ALTER TABLE `revision` CHANGE `timestamp` `timestamp` timestamp(6) NOT NULL DEFAULT current_timestamp(6);
+      rollback: # no need
+
+  - changeSet:
+      id: v48.00-011
+      author: qnkhuat
+      comment: Index revision.most_recent
+      changes:
+        - createIndex:
+            tableName: revision
+            indexName: idx_revision_most_recent
+            columns:
+              column:
+                name: most_recent
+
   - changeSet:
       id: v48.00-013
       author: qnkhuat
diff --git a/src/metabase/api/collection.clj b/src/metabase/api/collection.clj
index cf355e47fce9ec604656251e5d85d7d5c2801291..e406026118da197b6ec12ad9afffc34131995f7c 100644
--- a/src/metabase/api/collection.clj
+++ b/src/metabase/api/collection.clj
@@ -330,17 +330,10 @@
                     dataset?
                     (conj :c.database_id))
        :from      [[:report_card :c]]
-       ;; todo: should there be a flag, or a realized view?
-       :left-join [[{:select    [:r1.*]
-                     :from      [[:revision :r1]]
-                     :left-join [[:revision :r2] [:and
-                                                  [:= :r1.model_id :r2.model_id]
-                                                  [:= :r1.model :r2.model]
-                                                  [:< :r1.id :r2.id]]]
-                     :where     [:and
-                                 [:= :r2.id nil]
-                                 [:= :r1.model (h2x/literal "Card")]]} :r]
-                   [:= :r.model_id :c.id]
+       :left-join [[:revision :r] [:and
+                                   [:= :r.model_id :c.id]
+                                   [:= :r.most_recent true]
+                                   [:= :r.model (h2x/literal "Card")]]
                    [:core_user :u] [:= :u.id :r.user_id]]
        :where     [:and
                    [:= :collection_id (:id collection)]
@@ -419,16 +412,10 @@
                    [:u.last_name :last_edit_last_name]
                    [:r.timestamp :last_edit_timestamp]]
        :from      [[:report_dashboard :d]]
-       :left-join [[{:select    [:r1.*]
-                     :from      [[:revision :r1]]
-                     :left-join [[:revision :r2] [:and
-                                                  [:= :r1.model_id :r2.model_id]
-                                                  [:= :r1.model :r2.model]
-                                                  [:< :r1.id :r2.id]]]
-                     :where     [:and
-                                 [:= :r2.id nil]
-                                 [:= :r1.model (h2x/literal "Dashboard")]]} :r]
-                   [:= :r.model_id :d.id]
+       :left-join [[:revision :r] [:and
+                                   [:= :r.model_id :d.id]
+                                   [:= :r.most_recent true]
+                                   [:= :r.model (h2x/literal "Dashboard")]]
                    [:core_user :u] [:= :u.id :r.user_id]]
        :where     [:and
                    [:= :collection_id (:id collection)]
diff --git a/src/metabase/api/search.clj b/src/metabase/api/search.clj
index 023a5d1c1ccf1b5fe743eebe131491a26bc007b8..99d20416afc8f0cd097a81ee0f467d8cb292bcbd 100644
--- a/src/metabase/api/search.clj
+++ b/src/metabase/api/search.clj
@@ -2,9 +2,7 @@
   (:require
    [cheshire.core :as json]
    [compojure.core :refer [GET]]
-   [flatland.ordered.map :as ordered-map]
    [honey.sql.helpers :as sql.helpers]
-   [malli.core :as mc]
    [medley.core :as m]
    [metabase.analytics.snowplow :as snowplow]
    [metabase.api.common :as api]
@@ -12,14 +10,15 @@
    [metabase.db.query :as mdb.query]
    [metabase.models.collection :as collection]
    [metabase.models.interface :as mi]
-   [metabase.models.permissions :as perms]
    [metabase.public-settings.premium-features :as premium-features]
-   [metabase.search.config :as search-config]
+   [metabase.search.config :as search.config :refer [SearchableModel SearchContext]]
+   [metabase.search.filter :as search.filter]
    [metabase.search.scoring :as scoring]
-   [metabase.search.util :as search-util]
+   [metabase.search.util :as search.util]
    [metabase.server.middleware.offset-paging :as mw.offset-paging]
    [metabase.util :as u]
    [metabase.util.honey-sql-2 :as h2x]
+   [metabase.util.i18n :refer [deferred-tru]]
    [metabase.util.log :as log]
    [metabase.util.malli :as mu]
    [metabase.util.malli.schema :as ms]
@@ -29,90 +28,15 @@
 
 (set! *warn-on-reflection* true)
 
-(def ^:private SearchableModel
-  (into [:enum] search-config/all-models))
-
-(def ^:private SearchContext
-  "Map with the various allowed search parameters, used to construct the SQL query."
-  (mc/schema
-    [:map {:closed true}
-     [:search-string                       [:maybe ms/NonBlankString]]
-     [:archived?                           :boolean]
-     [:current-user-perms                  [:set perms/PathSchema]]
-     [:models             {:optional true} [:maybe [:set SearchableModel]]]
-     [:table-db-id        {:optional true} [:maybe ms/Int]]
-     [:limit-int          {:optional true} [:maybe ms/Int]]
-     [:offset-int         {:optional true} [:maybe ms/Int]]]))
-
 (def ^:private HoneySQLColumn
   [:or
    :keyword
    [:tuple :any :keyword]])
 
-;;; +----------------------------------------------------------------------------------------------------------------+
-;;; |                                            Columns for each Entity                                             |
-;;; +----------------------------------------------------------------------------------------------------------------+
-
-(def ^:private all-search-columns
-  "All columns that will appear in the search results, and the types of those columns. The generated search query is a
-  `UNION ALL` of the queries for each different entity; it looks something like:
-
-    SELECT 'card' AS model, id, cast(NULL AS integer) AS table_id, ...
-    FROM report_card
-    UNION ALL
-    SELECT 'metric' as model, id, table_id, ...
-    FROM metric
-
-  Columns that aren't used in any individual query are replaced with `SELECT cast(NULL AS <type>)` statements. (These
-  are cast to the appropriate type because Postgres will assume `SELECT NULL` is `TEXT` by default and will refuse to
-  `UNION` two columns of two different types.)"
-  (ordered-map/ordered-map
-   ;; returned for all models. Important to be first for changing model for dataset
-   :model               :text
-   :id                  :integer
-   :name                :text
-   :display_name        :text
-   :description         :text
-   :archived            :boolean
-   ;; returned for Card, Dashboard, and Collection
-   :collection_id       :integer
-   :collection_name     :text
-   :collection_authority_level :text
-   ;; returned for Card and Dashboard
-   :collection_position :integer
-   :bookmark            :boolean
-   ;; returned for everything except Collection
-   :updated_at          :timestamp
-   ;; returned for Card only
-   :dashboardcard_count :integer
-   :moderated_status    :text
-   ;; returned for Metric and Segment
-   :table_id            :integer
-   :table_schema        :text
-   :table_name          :text
-   :table_description   :text
-   ;; returned for Metric, Segment, and Action
-   :database_id         :integer
-   ;; returned for Database and Table
-   :initial_sync_status :text
-   ;; returned for Action
-   :model_id            :integer
-   :model_name          :text
-   ;; returned for indexed-entity
-   :pk_ref              :text
-   :model_index_id      :integer))
-
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                               Shared Query Logic                                               |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(def ^:private true-clause [:inline [:= 1 1]])
-(def ^:private false-clause [:inline [:= 0 1]])
-
-(mu/defn ^:private model->alias :- keyword?
-  [model :- SearchableModel]
-  (-> model search-config/model-to-db-model :alias))
-
 (mu/defn ^:private ->column-alias :- keyword?
   "Returns the column name. If the column is aliased, i.e. [`:original_name` `:aliased_name`], return the aliased
   column name"
@@ -126,7 +50,7 @@
   prefixed with the `model` name so that it can be used in criteria. Projects a `nil` for columns the `model` doesn't
   have and doesn't modify aliases."
   [model :- SearchableModel, col-alias->honeysql-clause :- [:map-of :keyword HoneySQLColumn]]
-  (for [[search-col col-type] all-search-columns
+  (for [[search-col col-type] search.config/all-search-columns
         :let                  [maybe-aliased-col (get col-alias->honeysql-clause search-col)]]
     (cond
       (= search-col :model)
@@ -138,7 +62,7 @@
 
       ;; This is a column reference, need to add the table alias to the column
       maybe-aliased-col
-      (keyword (name (model->alias model)) (name maybe-aliased-col))
+      (search.config/column-with-model-alias model maybe-aliased-col)
 
       ;; This entity is missing the column, project a null for that column value. For Postgres and H2, cast it to the
       ;; correct type, e.g.
@@ -154,84 +78,30 @@
 (mu/defn ^:private select-clause-for-model :- [:sequential HoneySQLColumn]
   "The search query uses a `union-all` which requires that there be the same number of columns in each of the segments
   of the query. This function will take the columns for `model` and will inject constant `nil` values for any column
-  missing from `entity-columns` but found in `all-search-columns`."
+  missing from `entity-columns` but found in `search.config/all-search-columns`."
   [model :- SearchableModel]
-  (let [entity-columns                (search-config/columns-for-model model)
+  (let [entity-columns                (search.config/columns-for-model model)
         column-alias->honeysql-clause (m/index-by ->column-alias entity-columns)
         cols-or-nils                  (canonical-columns model column-alias->honeysql-clause)]
     cols-or-nils))
 
 (mu/defn ^:private from-clause-for-model :- [:tuple [:tuple :keyword :keyword]]
   [model :- SearchableModel]
-  (let [{:keys [db-model alias]} (get search-config/model-to-db-model model)]
+  (let [{:keys [db-model alias]} (get search.config/model-to-db-model model)]
     [[(t2/table-name db-model) alias]]))
 
-(defmulti ^:private archived-where-clause
-  {:arglists '([model archived?])}
-  (fn [model _] model))
-
-(defmethod archived-where-clause :default
-  [model archived?]
-  [:= (keyword (name (model->alias model)) "archived") archived?])
-
-;; Databases can't be archived
-(defmethod archived-where-clause "database"
-  [_model archived?]
-  (if-not archived?
-    true-clause
-    false-clause))
-
-(defmethod archived-where-clause "indexed-entity"
-  [_model archived?]
-  (if-not archived?
-    true-clause
-    false-clause))
-
-;; Table has an `:active` flag, but no `:archived` flag; never return inactive Tables
-(defmethod archived-where-clause "table"
-  [model archived?]
-  (if archived?
-    false-clause                        ; No tables should appear in archive searches
-    [:and
-     [:= (keyword (name (model->alias model)) "active") true]
-     [:= (keyword (name (model->alias model)) "visibility_type") nil]]))
-
-(defn- wildcard-match
-  [s]
-  (str "%" s "%"))
-
-(defn- search-string-clause
-  [model query searchable-columns]
-  (when query
-    (into [:or]
-          (for [column searchable-columns
-                token (search-util/tokenize (search-util/normalize query))]
-            (if (and (= model "indexed-entity") (premium-features/sandboxed-or-impersonated-user?))
-              [:= 0 1]
-
-              [:like
-               [:lower column]
-               (wildcard-match token)])))))
-
-(mu/defn ^:private base-where-clause-for-model :- [:fn (fn [x] (and (seq x) (#{:and :inline :=} (first x))))]
-  [model :- SearchableModel {:keys [search-string archived?]} :- SearchContext]
-  (let [archived-clause (archived-where-clause model archived?)
-        search-clause   (search-string-clause model search-string
-                                              (map (let [model-alias (name (model->alias model))]
-                                                     (fn [column]
-                                                       (keyword (str (name model-alias) "." (name column)))))
-                                                   (search-config/searchable-columns-for-model model)))]
-    (if search-clause
-      [:and archived-clause search-clause]
-      archived-clause)))
-
-(mu/defn ^:private base-query-for-model :- [:map {:closed true} [:select :any] [:from :any] [:where :any]]
+(mu/defn ^:private base-query-for-model :- [:map {:closed true}
+                                            [:select :any]
+                                            [:from :any]
+                                            [:where :any]
+                                            [:join {:optional true} :any]
+                                            [:left-join {:optional true} :any]]
   "Create a HoneySQL query map with `:select`, `:from`, and `:where` clauses for `model`, suitable for the `UNION ALL`
   used in search."
   [model :- SearchableModel context :- SearchContext]
-  {:select (select-clause-for-model model)
-   :from   (from-clause-for-model model)
-   :where  (base-where-clause-for-model model context)})
+  (-> {:select (select-clause-for-model model)
+       :from   (from-clause-for-model model)}
+      (search.filter/build-filters model context)))
 
 (mu/defn add-collection-join-and-where-clauses
   "Add a `WHERE` clause to the query to only return Collections the Current User has access to; join against Collection
@@ -268,6 +138,40 @@
     (sql.helpers/where query [:= id :database_id])
     query))
 
+(mu/defn ^:private replace-select :- :map
+  "Replace a select from query that has alias is `target-alias` with the `with` column, throw an error if
+  can't find the target select.
+
+  This works with the assumption that `query` contains a list of select from [[select-clause-for-model]],
+  and some of them are dummy column casted to the correct type.
+
+  This function then will replace the dummy column with alias is `target-alias` with the `with` column."
+  [query        :- :map
+   target-alias :- :keyword
+   with         :- [:or :keyword [:sequential :any]]]
+  (let [selects (:select query)
+        idx     (first (keep-indexed (fn [index item]
+                                       (when (and (coll? item)
+                                                  (= (last item) target-alias))
+                                         index))
+                                     selects))]
+    (if (some? idx)
+      (assoc query :select (m/replace-nth idx with selects))
+      (throw (ex-info "Failed to replace selector" {:status-code  400
+                                                    :target-alias target-alias
+                                                    :with         with})))))
+
+(mu/defn ^:private with-last-editing-info :- :map
+  [query :- :map
+   model :- [:enum "card" "dataset" "dashboard" "metric"]]
+  (-> query
+       (replace-select :last_editor_id [:r.user_id :last_editor_id])
+       (replace-select :last_edited_at [:r.timestamp :last_edited_at])
+       (sql.helpers/left-join [:revision :r]
+                              [:and [:= :r.model_id (search.config/column-with-model-alias model :id)]
+                               [:= :r.most_recent true]
+                               [:= :r.model (search.config/search-model->revision-model model)]])))
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                      Search Queries for each Toucan Model                                      |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -277,15 +181,17 @@
   (fn [model _] model))
 
 (mu/defn ^:private shared-card-impl
-  [dataset? :- :boolean search-ctx :- SearchContext]
+  [model      :- [:enum "card" "dataset"]
+   search-ctx :- SearchContext]
   (-> (base-query-for-model "card" search-ctx)
-      (update :where (fn [where] [:and [:= :card.dataset dataset?] where]))
+      (update :where (fn [where] [:and [:= :card.dataset (= "dataset" model)] where]))
       (sql.helpers/left-join [:card_bookmark :bookmark]
                              [:and
                               [:= :bookmark.card_id :card.id]
                               [:= :bookmark.user_id api/*current-user-id*]])
       (add-collection-join-and-where-clauses :card.collection_id search-ctx)
-      (add-card-db-id-clause (:table-db-id search-ctx))))
+      (add-card-db-id-clause (:table-db-id search-ctx))
+      (with-last-editing-info model)))
 
 (defmethod search-query-for-model "action"
   [model search-ctx]
@@ -296,13 +202,14 @@
                              [:= :query_action.action_id :action.id])
       (add-collection-join-and-where-clauses :model.collection_id search-ctx)))
 
+
 (defmethod search-query-for-model "card"
   [_model search-ctx]
-  (shared-card-impl false search-ctx))
+  (shared-card-impl "card" search-ctx))
 
 (defmethod search-query-for-model "dataset"
   [_model search-ctx]
-  (-> (shared-card-impl true search-ctx)
+  (-> (shared-card-impl "dataset" search-ctx)
       (update :select (fn [columns]
                         (cons [(h2x/literal "dataset") :model] (rest columns))))))
 
@@ -326,12 +233,14 @@
                              [:and
                               [:= :bookmark.dashboard_id :dashboard.id]
                               [:= :bookmark.user_id api/*current-user-id*]])
-      (add-collection-join-and-where-clauses :dashboard.collection_id search-ctx)))
+      (add-collection-join-and-where-clauses :dashboard.collection_id search-ctx)
+      (with-last-editing-info model)))
 
 (defmethod search-query-for-model "metric"
   [model search-ctx]
   (-> (base-query-for-model model search-ctx)
-      (sql.helpers/left-join [:metabase_table :table] [:= :metric.table_id :table.id])))
+      (sql.helpers/left-join [:metabase_table :table] [:= :metric.table_id :table.id])
+      (with-last-editing-info model)))
 
 (defmethod search-query-for-model "indexed-entity"
   [model search-ctx]
@@ -357,7 +266,7 @@
            {:select (:select base-query)
             :from   [[(merge
                        base-query
-                       {:select [:id :schema :db_id :name :description :display_name :updated_at :initial_sync_status
+                       {:select [:id :schema :db_id :name :description :display_name :created_at :updated_at :initial_sync_status
                                  [(h2x/concat (h2x/literal "/db/")
                                               :db_id
                                               (h2x/literal "/schema/")
@@ -377,8 +286,8 @@
 (defn order-clause
   "CASE expression that lets the results be ordered by whether they're an exact (non-fuzzy) match or not"
   [query]
-  (let [match             (wildcard-match (search-util/normalize query))
-        columns-to-search (->> all-search-columns
+  (let [match             (search.util/wildcard-match (search.util/normalize query))
+        columns-to-search (->> search.config/all-search-columns
                                (filter (fn [[_k v]] (= v :text)))
                                (map first)
                                (remove #{:collection_authority_level :moderated_status
@@ -410,36 +319,54 @@
   [instance]
   (mi/can-read? instance))
 
-(mu/defn query-model-set
+(mu/defn query-model-set :- [:set SearchableModel]
   "Queries all models with respect to query for one result to see if we get a result or not"
   [search-ctx :- SearchContext]
-  ;; mapv used to realize lazy sequence. In web request, user bindings exist for entirety of request and we realize on
-  ;; serialization. At repl, we print lazy sequence after bindings have elapsed and you get unbound user errors
-  (mapv #(get (first %) :model)
-        (filter not-empty
-                (for [model search-config/all-models
-                      :let [search-query     (search-query-for-model model search-ctx)
-                            query-with-limit (when search-query
-                                               (sql.helpers/limit search-query 1))]
-                      :when query-with-limit]
-                  (mdb.query/query query-with-limit)))))
+  (let [model-queries (for [model (search.filter/search-context->applicable-models
+                                   (assoc search-ctx :models search.config/all-models))]
+                        {:nest (sql.helpers/limit (search-query-for-model model search-ctx) 1)})
+        query         (when (pos-int? (count model-queries))
+                        {:select [:*]
+                         :from   [[{:union-all model-queries} :dummy_alias]]})]
+    (set (some->> query
+                  mdb.query/query
+                  (map :model)
+                  set))))
 
 (mu/defn ^:private full-search-query
   "Postgres 9 is not happy with the type munging it needs to do to make the union-all degenerate down to trivial case of
   one model without errors. Therefore we degenerate it down for it"
   [search-ctx :- SearchContext]
-  (let [models       (or (:models search-ctx)
-                         search-config/all-models)
-        sql-alias    :alias_is_required_by_sql_but_not_needed_here
+  (let [models       (:models search-ctx)
         order-clause [((fnil order-clause "") (:search-string search-ctx))]]
-    (if (= (count models) 1)
-      (search-query-for-model (first models) search-ctx)
-      {:select   [:*]
-       :from     [[{:union-all (vec (for [model models
-                                          :let  [query (search-query-for-model model search-ctx)]
-                                          :when (seq query)]
-                                      query))} sql-alias]]
-       :order-by order-clause})))
+    (cond
+     (= (count models) 0)
+     {:select [nil]}
+
+     (= (count models) 1)
+     (search-query-for-model (first models) search-ctx)
+
+     :else
+     {:select   [:*]
+      :from     [[{:union-all (vec (for [model models
+                                         :let  [query (search-query-for-model model search-ctx)]
+                                         :when (seq query)]
+                                     query))} :alias_is_required_by_sql_but_not_needed_here]]
+      :order-by order-clause})))
+
+(defn- hydrate-user-metadata
+  "Hydrate common-name for last_edited_by and created_by from result."
+  [results]
+  (let [user-ids             (set (flatten (for [result results]
+                                             (remove nil? ((juxt :last_editor_id :creator_id) result)))))
+        user-id->common-name (if (pos? (count user-ids))
+                               (t2/select-pk->fn :common_name [:model/User :id :first_name :last_name :email] :id [:in user-ids])
+                               {})]
+    (mapv (fn [{:keys [creator_id last_editor_id] :as result}]
+            (assoc result
+                   :creator_common_name (get user-id->common-name creator_id)
+                   :last_editor_common_name (get user-id->common-name last_editor_id)))
+          results)))
 
 (mu/defn ^:private search
   "Builds a search query that includes all the searchable entities and runs it"
@@ -449,9 +376,9 @@
                                        (u/pprint-to-str search-query)
                                        (mdb.query/format-sql (first (mdb.query/compile search-query))))
         to-toucan-instance (fn [row]
-                             (let [model (-> row :model search-config/model-to-db-model :db-model)]
+                             (let [model (-> row :model search.config/model-to-db-model :db-model)]
                                (t2.instance/instance model row)))
-        reducible-results  (mdb.query/reducible-query search-query :max-rows search-config/*db-max-results*)
+        reducible-results  (mdb.query/reducible-query search-query :max-rows search.config/*db-max-results*)
         xf                 (comp
                             (map t2.realize/realize)
                             (map to-toucan-instance)
@@ -463,14 +390,14 @@
                             (map #(update % :pk_ref json/parse-string))
                             (map (partial scoring/score-and-result (:search-string search-ctx)))
                             (filter #(pos? (:score %))))
-        total-results      (scoring/top-results reducible-results search-config/max-filtered-results xf)]
+        total-results      (hydrate-user-metadata (scoring/top-results reducible-results search.config/max-filtered-results xf))]
     ;; We get to do this slicing and dicing with the result data because
     ;; the pagination of search is for UI improvement, not for performance.
     ;; We intend for the cardinality of the search results to be below the default max before this slicing occurs
     {:total            (count total-results)
      :data             (cond->> total-results
-                         (some?     (:offset-int search-ctx)) (drop (:offset-int search-ctx))
-                         (some?     (:limit-int search-ctx)) (take (:limit-int search-ctx)))
+                         (some? (:offset-int search-ctx)) (drop (:offset-int search-ctx))
+                         (some? (:limit-int search-ctx)) (take (:limit-int search-ctx)))
      :available_models (query-model-set search-ctx)
      :limit            (:limit-int search-ctx)
      :offset           (:offset-int search-ctx)
@@ -481,27 +408,72 @@
 ;;; |                                                    Endpoint                                                    |
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
-(mu/defn ^:private search-context :- SearchContext
-  [search-string   :- [:maybe ms/NonBlankString]
-   archived-string :- [:maybe ms/BooleanString]
-   table-db-id     :- [:maybe ms/PositiveInt]
-   models          :- [:maybe [:or SearchableModel [:sequential SearchableModel]]]
-   limit           :- [:maybe ms/PositiveInt]
-   offset          :- [:maybe ms/IntGreaterThanOrEqualToZero]]
-  (cond-> {:search-string      search-string
-           :archived?          (Boolean/parseBoolean archived-string)
-           :current-user-perms @api/*current-user-permissions-set*}
-    (some? table-db-id) (assoc :table-db-id table-db-id)
-    (some? models)      (assoc :models
-                               (apply hash-set (if (vector? models) models [models])))
-    (some? limit)       (assoc :limit-int limit)
-    (some? offset)      (assoc :offset-int offset)))
+(mu/defn ^:private search-context
+  [{:keys [archived
+           created-at
+           created-by
+           last-edited-at
+           last-edited-by
+           limit
+           models
+           offset
+           search-string
+           table-db-id
+           search-native-query
+           verified]}      :- [:map {:closed true}
+                               [:search-string                        [:maybe ms/NonBlankString]]
+                               [:models                               [:maybe [:set SearchableModel]]]
+                               [:archived            {:optional true} [:maybe :boolean]]
+                               [:created-at          {:optional true} [:maybe ms/NonBlankString]]
+                               [:created-by          {:optional true} [:maybe [:set ms/PositiveInt]]]
+                               [:last-edited-at      {:optional true} [:maybe ms/NonBlankString]]
+                               [:last-edited-by      {:optional true} [:maybe [:set ms/PositiveInt]]]
+                               [:limit               {:optional true} [:maybe ms/Int]]
+                               [:offset              {:optional true} [:maybe ms/Int]]
+                               [:table-db-id         {:optional true} [:maybe ms/PositiveInt]]
+                               [:search-native-query {:optional true} [:maybe boolean?]]
+                               [:verified            {:optional true} [:maybe true?]]]]
+  (when (some? verified)
+    (premium-features/assert-has-any-features
+     [:content-verification :official-collections]
+     (deferred-tru "Content Management or Official Collections")))
+  (let [models (if (string? models) [models] models)
+        ctx    (cond-> {:search-string      search-string
+                        :current-user-perms @api/*current-user-permissions-set*
+                        :archived?          (boolean archived)
+                        :models             models}
+                 (some? created-at)          (assoc :created-at created-at)
+                 (seq created-by)            (assoc :created-by created-by)
+                 (some? last-edited-at)      (assoc :last-edited-at last-edited-at)
+                 (seq last-edited-by)        (assoc :last-edited-by last-edited-by)
+                 (some? table-db-id)         (assoc :table-db-id table-db-id)
+                 (some? limit)               (assoc :limit-int limit)
+                 (some? offset)              (assoc :offset-int offset)
+                 (some? search-native-query) (assoc :search-native-query search-native-query)
+                 (some? verified)            (assoc :verified verified))]
+    (assoc ctx :models (search.filter/search-context->applicable-models ctx))))
 
 (api/defendpoint GET "/models"
   "Get the set of models that a search query will return"
-  [q archived-string table-db-id]
-  {table-db-id [:maybe ms/PositiveInt]}
-  (query-model-set (search-context q archived-string table-db-id nil nil nil)))
+  [q archived table-db-id created_at created_by last_edited_at last_edited_by search_native_query verified]
+  {archived            [:maybe ms/BooleanValue]
+   table-db-id         [:maybe ms/PositiveInt]
+   created_at          [:maybe ms/NonBlankString]
+   created_by          [:maybe [:or ms/PositiveInt [:sequential ms/PositiveInt]]]
+   last_edited_at      [:maybe ms/PositiveInt]
+   last_edited_by      [:maybe [:or ms/PositiveInt [:sequential ms/PositiveInt]]]
+   search_native_query [:maybe true?]
+   verified            [:maybe true?]}
+  (query-model-set (search-context {:search-string       q
+                                    :archived            archived
+                                    :table-db-id         table-db-id
+                                    :created-at          created_at
+                                    :created-by          (set (u/one-or-many created_by))
+                                    :last-edited-at      last_edited_at
+                                    :last-edited-by      (set (u/one-or-many last_edited_by))
+                                    :search-native-query search_native_query
+                                    :verified            verified
+                                    :models              search.config/all-models})))
 
 (api/defendpoint GET "/"
   "Search within a bunch of models for the substring `q`.
@@ -512,20 +484,36 @@
   to `table_db_id`.
   To specify a list of models, pass in an array to `models`.
   "
-  [q archived table_db_id models]
-  {q            [:maybe ms/NonBlankString]
-   archived     [:maybe ms/BooleanString]
-   table_db_id  [:maybe ms/PositiveInt]
-   models       [:maybe [:or SearchableModel [:sequential SearchableModel]]]}
+  [q archived created_at created_by table_db_id models last_edited_at last_edited_by search_native_query verified]
+  {q                   [:maybe ms/NonBlankString]
+   archived            [:maybe :boolean]
+   table_db_id         [:maybe ms/PositiveInt]
+   models              [:maybe [:or SearchableModel [:sequential SearchableModel]]]
+   created_at          [:maybe ms/NonBlankString]
+   created_by          [:maybe [:or ms/PositiveInt [:sequential ms/PositiveInt]]]
+   last_edited_at      [:maybe ms/NonBlankString]
+   last_edited_by      [:maybe [:or ms/PositiveInt [:sequential ms/PositiveInt]]]
+   search_native_query [:maybe true?]
+   verified            [:maybe true?]}
   (api/check-valid-page-params mw.offset-paging/*limit* mw.offset-paging/*offset*)
   (let [start-time (System/currentTimeMillis)
+        models-set (cond
+                    (nil? models)    search.config/all-models
+                    (string? models) #{models}
+                    :else            (set models))
         results    (search (search-context
-                            q
-                            archived
-                            table_db_id
-                            models
-                            mw.offset-paging/*limit*
-                            mw.offset-paging/*offset*))
+                            {:search-string       q
+                             :archived            archived
+                             :created-at          created_at
+                             :created-by          (set (u/one-or-many created_by))
+                             :last-edited-at      last_edited_at
+                             :last-edited-by      (set (u/one-or-many last_edited_by))
+                             :table-db-id         table_db_id
+                             :models              models-set
+                             :limit               mw.offset-paging/*limit*
+                             :offset              mw.offset-paging/*offset*
+                             :search-native-query search_native_query
+                             :verified            verified}))
         duration   (- (System/currentTimeMillis) start-time)]
     ;; Only track global searches
     (when (and (nil? models)
diff --git a/src/metabase/driver/common/parameters/dates.clj b/src/metabase/driver/common/parameters/dates.clj
index 42e049abf9d079445b410915103b80ef3473c942..e4c05127968f4d066df814f8309c138abd9af293 100644
--- a/src/metabase/driver/common/parameters/dates.clj
+++ b/src/metabase/driver/common/parameters/dates.clj
@@ -12,8 +12,7 @@
    [metabase.util.date-2 :as u.date]
    [metabase.util.i18n :refer [tru]]
    [metabase.util.malli :as mu]
-   [metabase.util.malli.schema :as ms]
-   [schema.core :as s])
+   [metabase.util.malli.schema :as ms])
   (:import
    (java.time.temporal Temporal)))
 
@@ -40,7 +39,7 @@
 
 (defn- day-range
   [start end]
-  {:start start, :end end})
+  {:start start :end end :unit :day})
 
 (defn- comparison-range
   ([t unit]
@@ -52,7 +51,8 @@
   ([start end unit resolution]
    (merge
     (u.date/comparison-range start unit :>= {:resolution resolution})
-    (u.date/comparison-range end   unit :<= {:resolution resolution, :end :inclusive}))))
+    (u.date/comparison-range end   unit :<= {:resolution resolution, :end :inclusive})
+    {:unit unit})))
 
 (defn- second-range
   [start end]
@@ -87,7 +87,8 @@
                                             "Q3" 3
                                             "Q4" 4))]
     {:start (.atDay year-quarter 1)
-     :end   (.atEndOfQuarter year-quarter)}))
+     :end   (.atEndOfQuarter year-quarter)
+     :unit  :quarter}))
 
 (def ^:private operations-by-date-unit
   {"second"  {:unit-range second-range
@@ -108,8 +109,7 @@
               :to-period  t/years}})
 
 (defn- maybe-reduce-resolution [unit dt]
-  (if
-    (contains? #{"second" "minute" "hour"} unit)
+  (if (contains? #{"second" "minute" "hour"} unit)
     dt
     ; for units that are a day or longer, convert back to LocalDate
     (t/local-date dt)))
@@ -167,7 +167,8 @@
     :range  (fn [_ dt]
               (let [dt-res (t/local-date dt)]
                 {:start dt-res,
-                 :end   dt-res}))
+                 :end   dt-res
+                 :unit  :day}))
     :filter (fn [_ field-clause]
               [:= (with-temporal-unit-if-field field-clause :day) [:relative-datetime :current]])}
 
@@ -175,7 +176,8 @@
     :range  (fn [_ dt]
               (let [dt-res (t/local-date dt)]
                 {:start (t/minus dt-res (t/days 1))
-                 :end   (t/minus dt-res (t/days 1))}))
+                 :end   (t/minus dt-res (t/days 1))
+                 :unit  :day}))
     :filter (fn [_ field-clause]
               [:= (with-temporal-unit-if-field field-clause :day) [:relative-datetime -1 :day]])}
 
@@ -291,6 +293,13 @@
   "Regex to match date exclusion values, e.g. exclude-days-Mon, exclude-months-Jan, etc."
   (re-pattern (str "exclude-" temporal-units-regex #"s-([-\p{Alnum}]+)")))
 
+(defn- absolute-date->unit
+  [date-string]
+  (if (str/includes? date-string "T")
+    ;; on the UI you can specify the time up to the minute, so we use minute here
+    :minute
+    :day))
+
 (def ^:private absolute-date-string-decoders
   ;; year and month
   [{:parser (regex->parser #"([0-9]{4}-[0-9]{2})" [:date])
@@ -308,20 +317,20 @@
    ;; single day
    {:parser (regex->parser #"([0-9-T:]+)" [:date])
     :range  (fn [{:keys [date]} _]
-              {:start date, :end date})
+              {:start date :end date :unit (absolute-date->unit date)})
     :filter (fn [{:keys [date]} field-clause]
               (let [iso8601date (->iso-8601-date date)]
                 [:= (with-temporal-unit-if-field field-clause :day) iso8601date]))}
    ;; day range
    {:parser (regex->parser #"([0-9-T]+)~([0-9-T]+)" [:date-1 :date-2])
     :range  (fn [{:keys [date-1 date-2]} _]
-              {:start date-1, :end date-2})
+              {:start date-1 :end date-2 :unit (absolute-date->unit date-1)})
     :filter (fn [{:keys [date-1 date-2]} field-clause]
               [:between (with-temporal-unit-if-field field-clause :day) (->iso-8601-date date-1) (->iso-8601-date date-2)])}
    ;; datetime range
    {:parser (regex->parser #"([0-9-T:]+)~([0-9-T:]+)" [:date-1 :date-2])
     :range  (fn [{:keys [date-1 date-2]} _]
-              {:start date-1, :end date-2})
+              {:start date-1, :end date-2 :unit (absolute-date->unit date-1)})
     :filter (fn [{:keys [date-1 date-2]} field-clause]
               [:between (with-temporal-unit-if-field field-clause :default)
                (->iso-8601-date-time date-1)
@@ -329,13 +338,13 @@
    ;; before day
    {:parser (regex->parser #"~([0-9-T:]+)" [:date])
     :range  (fn [{:keys [date]} _]
-              {:end date})
+              {:end date :unit (absolute-date->unit date)})
     :filter (fn [{:keys [date]} field-clause]
               [:< (with-temporal-unit-if-field field-clause :day) (->iso-8601-date date)])}
    ;; after day
    {:parser (regex->parser #"([0-9-T:]+)~" [:date])
     :range  (fn [{:keys [date]} _]
-              {:start date})
+              {:start date :unit (absolute-date->unit date)})
     :filter (fn [{:keys [date]} field-clause]
               [:> (with-temporal-unit-if-field field-clause :day) (->iso-8601-date date)])}
    ;; exclusions
@@ -363,38 +372,58 @@
             (parser-result-decoder parser-result decoder-param)))
         decoders))
 
+(def ^:private TemporalUnit
+  (into [:enum] u.date/add-units))
+
 (def ^:private TemporalRange
-  {(s/optional-key :start) Temporal, (s/optional-key :end) Temporal})
+  [:map
+   [:start {:optional true} [:fn #(instance? Temporal %)]]
+   [:end   {:optional true} [:fn #(instance? Temporal %)]]
+   [:unit                   TemporalUnit]])
 
-(s/defn ^:private adjust-inclusive-range-if-needed :- (s/maybe TemporalRange)
+(mu/defn ^:private adjust-inclusive-range-if-needed :- [:maybe TemporalRange]
   "Make an inclusive date range exclusive as needed."
-  [{:keys [inclusive-start? inclusive-end?]}, {:keys [start end]} :- (s/maybe TemporalRange)]
-  (merge
-   (when start
-     {:start (if inclusive-start?
-               start
-               (u.date/add start :day -1))})
-   (when end
-     {:end (if inclusive-end?
-             end
-             (u.date/add end :day 1))})))
+  [{:keys [inclusive-start? inclusive-end?]} temporal-range :- [:maybe TemporalRange]]
+  (-> temporal-range
+      (m/update-existing :start #(if inclusive-start?
+                                   %
+                                   (u.date/add % (case (:unit temporal-range)
+                                                   (:year :quarter :month :week :day)
+                                                   :day
+                                                   (:unit temporal-range)) -1)))
+      (m/update-existing :end #(if inclusive-end?
+                                 %
+                                 (u.date/add % (case (:unit temporal-range)
+                                                   (:year :quarter :month :week :day)
+                                                   :day
+                                                   (:unit temporal-range)) 1)))))
 
 (def ^:private DateStringRange
   "Schema for a valid date range returned by `date-string->range`."
-  (-> {(s/optional-key :start) s/Str, (s/optional-key :end) s/Str}
-      (s/constrained seq
-                     "must have either :start or :end")
-      (s/constrained (fn [{:keys [start end]}]
-                       (or (not start)
-                           (not end)
-                           (not (pos? (compare start end)))))
-                     ":start must not come after :end")
-      (s/named "valid date range")))
-
-(s/defn date-string->range :- DateStringRange
+  [:and [:map {:closed true}
+         [:start {:optional true} ms/NonBlankString]
+         [:end   {:optional true} ms/NonBlankString]]
+   [:fn {:error/message "must have either :start or :end"}
+    (fn [{:keys [start end]}]
+      (or start end))]
+   [:fn {:error/message ":start must come before :end"}
+    (fn [{:keys [start end]}]
+      (or (not start)
+          (not end)
+          (not (pos? (compare start end)))))]])
+
+(defn- format-date-range
+  [date-range]
+  (-> date-range
+      (m/update-existing :start u.date/format)
+      (m/update-existing :end u.date/format)
+      (dissoc :unit)))
+
+(mu/defn date-string->range :- DateStringRange
   "Takes a string description of a date range such as `lastmonth` or `2016-07-15~2016-08-6` and returns a map with
-  `:start` and/or `:end` keys, as ISO-8601 *date* strings. By default, `:start` and `:end` are inclusive, e.g.
+  `:start` and/or `:end` keys, as ISO-8601 *date* strings. By default, `:start` and `:end` are inclusive,
 
+  e.g:
     (date-string->range \"past2days\") ; -> {:start \"2020-01-20\", :end \"2020-01-21\"}
 
   intended for use with SQL like
@@ -409,20 +438,21 @@
   ([date-string]
    (date-string->range date-string nil))
 
-  ([date-string  :- s/Str {:keys [inclusive-start? inclusive-end?]
-                           :or   {inclusive-start? true, inclusive-end? true}}]
+  ([date-string  :- ms/NonBlankString
+    {:keys [inclusive-start? inclusive-end?]
+     :or   {inclusive-start? true inclusive-end? true}}]
    (let [options {:inclusive-start? inclusive-start?, :inclusive-end? inclusive-end?}
          now (t/local-date-time)]
      ;; Relative dates respect the given time zone because a notion like "last 7 days" might mean a different range of
      ;; days depending on the user timezone
      (or (->> (execute-decoders relative-date-string-decoders :range now date-string)
               (adjust-inclusive-range-if-needed options)
-              (m/map-vals u.date/format))
+              format-date-range)
          ;; Absolute date ranges don't need the time zone conversion because in SQL the date ranges are compared
          ;; against the db field value that is casted granularity level of a day in the db time zone
          (->> (execute-decoders absolute-date-string-decoders :range nil date-string)
               (adjust-inclusive-range-if-needed options)
-              (m/map-vals u.date/format))
+              format-date-range)
          ;; if both of the decoders above fail, then the date string is invalid
          (throw (ex-info (tru "Don''t know how to parse date param ''{0}'' — invalid format" date-string)
                          {:param date-string
diff --git a/src/metabase/models/metric.clj b/src/metabase/models/metric.clj
index fe4947b29cf0344f0526d49a8e6af2d44cfa06f0..18baa736f9883e74cbedca21d1247364391285db 100644
--- a/src/metabase/models/metric.clj
+++ b/src/metabase/models/metric.clj
@@ -51,6 +51,10 @@
       (when (not= (:creator_id <>) (t2/select-one-fn :creator_id Metric :id id))
         (throw (UnsupportedOperationException. (tru "You cannot update the creator_id of a Metric.")))))))
 
+(t2/define-before-delete :model/Metric
+  [{:keys [id] :as _metric}]
+  (t2/delete! :model/Revision :model "Metric" :model_id id))
+
 (defmethod mi/perms-objects-set Metric
   [metric read-or-write]
   (let [table (or (:table metric)
diff --git a/src/metabase/models/params/custom_values.clj b/src/metabase/models/params/custom_values.clj
index f14b0322035041bb0a20f42e6e8742e3fe8899b3..2c45e7bf94ad5389e69dac99ea1fcd26f3c79c98 100644
--- a/src/metabase/models/params/custom_values.clj
+++ b/src/metabase/models/params/custom_values.clj
@@ -11,7 +11,7 @@
    [metabase.models.interface :as mi]
    [metabase.query-processor :as qp]
    [metabase.query-processor.util :as qp.util]
-   [metabase.search.util :as search]
+   [metabase.search.util :as search.util]
    [metabase.util :as u]
    [metabase.util.i18n :refer [tru]]
    [metabase.util.malli :as mu]
@@ -26,13 +26,11 @@
   - [[value1], [value2]]
   - [[value2, label2], [value2, label2]] - we search using label in this case"
   [query values]
-  (let [normalized-query (search/normalize query)]
-    (filter (fn [v]
-              (str/includes? (search/normalize (if (= (count v) 1)
-                                                 (first v)
-                                                 (second v)))
-                             normalized-query))
-            values)))
+  (let [normalized-query (search.util/normalize query)]
+    (filter (fn [v] (str/includes? (search.util/normalize (if (= (count v) 1)
+                                                            (first v)
+                                                            (second v)))
+                                   normalized-query)) values)))
 
 (defn- static-list-values
   [{values-source-options :values_source_config :as _param} query]
diff --git a/src/metabase/models/revision.clj b/src/metabase/models/revision.clj
index 336356d69ffe215d456965ca964ab081061f9f32..208da49be9a299c18c14b505cdd5b5660a91584e 100644
--- a/src/metabase/models/revision.clj
+++ b/src/metabase/models/revision.clj
@@ -74,7 +74,10 @@
 
 (t2/define-before-insert :model/Revision
   [revision]
-  (assoc revision :timestamp :%now :metabase_version config/mb-version-string))
+  (assoc revision
+         :timestamp :%now
+         :metabase_version config/mb-version-string
+         :most_recent true))
 
 (t2/define-before-update :model/Revision
   [_revision]
@@ -91,6 +94,28 @@
     (cond-> revision
       model (update :object (partial mi/do-after-select model)))))
 
+(defn- delete-old-revisions!
+  "Delete old revisions of `model` with `id` when there are more than `max-revisions` in the DB."
+  [model id]
+  (when-let [old-revisions (seq (drop max-revisions (t2/select-fn-vec :id :model/Revision
+                                                                      :model    (name model)
+                                                                      :model_id id
+                                                                      {:order-by [[:timestamp :desc]
+                                                                                  [:id :desc]]})))]
+    (t2/delete! :model/Revision :id [:in old-revisions])))
+
+(t2/define-after-insert :model/Revision
+  [revision]
+  (u/prog1 revision
+    (let [{:keys [id model model_id]} revision]
+      ;; Note 1: Update the last `most_recent revision` to false (not including the current revision)
+      ;; Note 2: We don't allow updating revision but this is a special case, so we by pass the check by
+      ;; updating directly with the table name
+      (t2/update! (t2/table-name :model/Revision)
+                  {:model model :model_id model_id :most_recent true :id [:not= id]}
+                  {:most_recent false})
+      (delete-old-revisions! model model_id))))
+
 ;;; # Functions
 
 (defn- revision-changes
@@ -144,17 +169,6 @@
         (recur (conj acc (add-revision-details model r1 r2))
                (conj more r2))))))
 
-(defn- delete-old-revisions!
-  "Delete old revisions of `model` with `id` when there are more than `max-revisions` in the DB."
-  [model id]
-  {:pre [(mdb.u/toucan-model? model) (integer? id)]}
-  (when-let [old-revisions (seq (drop max-revisions (map :id (t2/select [Revision :id]
-                                                               :model    (name model)
-                                                               :model_id id
-                                                               {:order-by [[:timestamp :desc]
-                                                                           [:id :desc]]}))))]
-    (t2/delete! Revision :id [:in old-revisions])))
-
 (defn push-revision!
   "Record a new Revision for `entity` with `id` if it's changed compared to the last revision.
   Returns `object` or `nil` if the object does not changed."
@@ -187,7 +201,6 @@
                  :is_creation  is-creation?
                  :is_reversion false
                  :message      message)
-     (delete-old-revisions! entity id)
      object)))
 
 (defn revert!
diff --git a/src/metabase/models/revision/last_edit.clj b/src/metabase/models/revision/last_edit.clj
index 367905804cff106e5b7535dad65c81feedf49497..23c157520aa4f8e4b032be554b95e88a8e53f434 100644
--- a/src/metabase/models/revision/last_edit.clj
+++ b/src/metabase/models/revision/last_edit.clj
@@ -11,9 +11,9 @@
    [clj-time.core :as time]
    [clojure.set :as set]
    [medley.core :as m]
-   [metabase.db.query :as mdb.query]
    [metabase.util.malli :as mu]
-   [metabase.util.malli.schema :as ms]))
+   [metabase.util.malli.schema :as ms]
+   [toucan2.core :as t2]))
 
 (def ^:private model->db-model {:card "Card" :dashboard "Dashboard"})
 
@@ -38,14 +38,13 @@
   `:card`. Gets the last edited information from the revisions table. If you need this information from a put route,
   use `@api/*current-user*` and a current timestamp since revisions are events and asynchronous."
   [{:keys [id] :as item} model :- [:enum :dashboard :card]]
-  (if-let [[updated-info] (seq (mdb.query/query {:select    [:u.id :u.email :u.first_name :u.last_name :r.timestamp]
-                                                 :from      [[:revision :r]]
-                                                 :left-join [[:core_user :u] [:= :u.id :r.user_id]]
-                                                 :where     [:and
-                                                             [:= :r.model (model->db-model model)]
-                                                             [:= :r.model_id id]]
-                                                 :order-by  [[:r.id :desc]]
-                                                 :limit     1}))]
+  (if-let [updated-info (t2/query-one {:select    [:u.id :u.email :u.first_name :u.last_name :r.timestamp]
+                                       :from      [[:revision :r]]
+                                       :left-join [[:core_user :u] [:= :u.id :r.user_id]]
+                                       :where     [:and
+                                                   [:= :r.most_recent true]
+                                                   [:= :r.model (model->db-model model)]
+                                                   [:= :r.model_id id]]})]
     (assoc item :last-edit-info updated-info)
     item))
 
@@ -69,27 +68,21 @@
   "Fetch edited info from the revisions table. Revision information is timestamp, user id, email, first and last
   name. Takes card-ids and dashboard-ids and returns a map structured like
 
-    {:card      {model_id {:id :email :first_name :last_name :timestamp}}
-     :dashboard {model_id {:id :email :first_name :last_name :timestamp}}}"
+  {:card      {card_id      {:id :email :first_name :last_name :timestamp}}
+   :dashboard {dashboard_id {:id :email :first_name :last_name :timestamp}}}"
   [{:keys [card-ids dashboard-ids]}]
   (when (seq (concat card-ids dashboard-ids))
-    ;; [:in :model_id []] generates bad sql so need to conditionally add it
-    (let [where-clause   (into [:or]
-                               (keep (fn [[model-name ids]]
-                                       (when (seq ids)
-                                         [:and [:= :model model-name] [:in :model_id ids]])))
-                               [["Card" card-ids]
-                                ["Dashboard" dashboard-ids]])
-          latest-changes (mdb.query/query {:select    [:u.id :u.email :u.first_name :u.last_name
-                                                       :r.model :r.model_id :r.timestamp]
-                                           :from      [[:revision :r]]
-                                           :left-join [[:core_user :u] [:= :u.id :r.user_id]]
-                                           :where     [:in :r.id
-                                                       ;; subselect for the max revision id for each item
-                                                       {:select   [[:%max.id :latest-revision-id]]
-                                                        :from     [:revision]
-                                                        :where    where-clause
-                                                        :group-by [:model :model_id]}]})]
+    (let [latest-changes (t2/query {:select    [:u.id :u.email :u.first_name :u.last_name
+                                                :r.model :r.model_id :r.timestamp]
+                                    :from      [[:revision :r]]
+                                    :left-join [[:core_user :u] [:= :u.id :r.user_id]]
+                                    :where     [:and [:= :r.most_recent true]
+                                                (into [:or]
+                                                      (keep (fn [[model-name ids]]
+                                                              (when (seq ids)
+                                                                [:and [:= :model model-name] [:in :model_id ids]])))
+                                                      [["Card" card-ids]
+                                                       ["Dashboard" dashboard-ids]])]})]
       (->> latest-changes
            (group-by :model)
            (m/map-vals (fn [model-changes]
diff --git a/src/metabase/moderation.clj b/src/metabase/moderation.clj
index f0cb7edd64047bf25ce7d2ccf30174dd9907022a..3ef73255267a50c217aceaa41057a085d3b12d9e 100644
--- a/src/metabase/moderation.clj
+++ b/src/metabase/moderation.clj
@@ -12,9 +12,7 @@
 (def moderated-item-type->model
   "Maps DB name of the moderated item type to the model symbol (used for t2/select and such)"
   {"card" 'Card
-   :card       'Card
-   "dashboard" 'Dashboard
-   :dashboard  'Dashboard})
+   :card  'Card})
 
 (defn- object->type
   "Convert a moderated item instance to the keyword stored in the database"
diff --git a/src/metabase/public_settings/premium_features.clj b/src/metabase/public_settings/premium_features.clj
index 72f11cd3958035a10f7679995e197f7dde20be92..3cc43c410518135c084117d86300197a6a647689 100644
--- a/src/metabase/public_settings/premium_features.clj
+++ b/src/metabase/public_settings/premium_features.clj
@@ -258,8 +258,7 @@
 
 (mu/defn assert-has-feature
   "Check if an token with `feature` is present. If not, throw an error with a message using `feature-name`.
-   `feature-name` should be a localized string unless used in a CLI context.
-
+  `feature-name` should be a localized string unless used in a CLI context.
   (assert-has-feature :sandboxes (tru \"Sandboxing\"))
   => throws an error with a message using \"Sandboxing\" as the feature name."
   [feature-flag :- keyword?
@@ -267,6 +266,13 @@
   (when-not (has-feature? feature-flag)
     (throw (ee-feature-error feature-name))))
 
+(mu/defn assert-has-any-features
+  "Check if has at least one of feature in `features`. Throw an error if none of the features are available."
+  [feature-flag :- [:sequential keyword?]
+   feature-name :- [:or string? mu/localized-string-schema]]
+  (when-not (some has-feature? feature-flag)
+    (throw (ee-feature-error feature-name))))
+
 (defn- default-premium-feature-getter [feature]
   (fn []
     (and config/ee-available?
diff --git a/src/metabase/search/config.clj b/src/metabase/search/config.clj
index d913c6dcd028272f3e6ade4da503b26d3a5fe903..41dbfdfafc3a8062a1f8cc583c395368fc04b40a 100644
--- a/src/metabase/search/config.clj
+++ b/src/metabase/search/config.clj
@@ -1,11 +1,15 @@
 (ns metabase.search.config
   (:require
-   [metabase.models
-    :refer [Action Card Collection Dashboard Database Metric
-            ModelIndexValue Segment Table]]
+   [cheshire.core :as json]
+   [clojure.string :as str]
+   [flatland.ordered.map :as ordered-map]
+   [malli.core :as mc]
+   [metabase.models.permissions :as perms]
    [metabase.models.setting :refer [defsetting]]
    [metabase.public-settings :as public-settings]
-   [metabase.util.i18n :refer [deferred-tru]]))
+   [metabase.util.i18n :refer [deferred-tru]]
+   [metabase.util.malli :as mu]
+   [metabase.util.malli.schema :as ms]))
 
 (defsetting search-typeahead-enabled
   (deferred-tru "Enable typeahead search in the {0} navbar?"
@@ -41,22 +45,128 @@
 
 (def model-to-db-model
   "Mapping from string model to the Toucan model backing it."
-  {"action"         {:db-model Action, :alias :action}
-   "card"           {:db-model Card, :alias :card}
-   "collection"     {:db-model Collection, :alias :collection}
-   "dashboard"      {:db-model Dashboard, :alias :dashboard}
-   "database"       {:db-model Database, :alias :database}
-   "dataset"        {:db-model Card, :alias :card}
-   "indexed-entity" {:db-model ModelIndexValue :alias :model-index-value}
-   "metric"         {:db-model Metric, :alias :metric}
-   "segment"        {:db-model Segment, :alias :segment}
-   "table"          {:db-model Table, :alias :table}})
+  {"action"         {:db-model :model/Action :alias :action}
+   "card"           {:db-model :model/Card :alias :card}
+   "collection"     {:db-model :model/Collection :alias :collection}
+   "dashboard"      {:db-model :model/Dashboard :alias :dashboard}
+   "database"       {:db-model :model/Database :alias :database}
+   "dataset"        {:db-model :model/Card :alias :card}
+   "indexed-entity" {:db-model :model/ModelIndexValue :alias :model-index-value}
+   "metric"         {:db-model :model/Metric :alias :metric}
+   "segment"        {:db-model :model/Segment :alias :segment}
+   "table"          {:db-model :model/Table :alias :table}})
 
 (def all-models
-  "All valid models to search for. The order of this list also influences the order of the results: items earlier in the
+  "Set of all valid models to search for. "
+  (set (keys model-to-db-model)))
+
+(def models-search-order
+  "The order of this list influences the order of the results: items earlier in the
   list will be ranked higher."
   ["dashboard" "metric" "segment" "indexed-entity" "card" "dataset" "collection" "table" "action" "database"])
 
+(assert (= all-models (set models-search-order)) "The models search order has to include all models")
+
+(defn search-model->revision-model
+  "Return the apporpriate revision model given a search model."
+  [model]
+  (case model
+    "dataset" (recur "card")
+    (str/capitalize model)))
+
+(defn model->alias
+  "Given a model string returns the model alias"
+  [model]
+  (-> model model-to-db-model :alias))
+
+(mu/defn column-with-model-alias :- keyword?
+  "Given a column and a model name, Return a keyword representing the column with the model alias prepended.
+
+  (column-with-model-alias \"card\" :id) => :card.id)"
+  [model-string :- ms/KeywordOrString
+   column       :- ms/KeywordOrString]
+  (keyword (str (name (model->alias model-string)) "." (name column))))
+
+(def SearchableModel
+  "Schema for searchable models"
+  (into [:enum] all-models))
+
+(def SearchContext
+  "Map with the various allowed search parameters, used to construct the SQL query."
+  (mc/schema
+   [:map {:closed true}
+    [:search-string                        [:maybe ms/NonBlankString]]
+    [:archived?                            :boolean]
+    [:current-user-perms                   [:set perms/PathSchema]]
+    [:models                               [:set SearchableModel]]
+    [:created-at          {:optional true} ms/NonBlankString]
+    [:created-by          {:optional true} [:set {:min 1} ms/PositiveInt]]
+    [:last-edited-at      {:optional true} ms/NonBlankString]
+    [:last-edited-by      {:optional true} [:set {:min 1} ms/PositiveInt]]
+    [:table-db-id         {:optional true} ms/PositiveInt]
+    [:limit-int           {:optional true} ms/Int]
+    [:offset-int          {:optional true} ms/Int]
+    [:search-native-query {:optional true} true?]
+    ;; true to search for verified items only,
+    ;; nil will return all items
+    [:verified            {:optional true} true?]]))
+
+
+(def all-search-columns
+  "All columns that will appear in the search results, and the types of those columns. The generated search query is a
+  `UNION ALL` of the queries for each different entity; it looks something like:
+
+    SELECT 'card' AS model, id, cast(NULL AS integer) AS table_id, ...
+    FROM report_card
+    UNION ALL
+    SELECT 'metric' as model, id, table_id, ...
+    FROM metric
+
+  Columns that aren't used in any individual query are replaced with `SELECT cast(NULL AS <type>)` statements. (These
+  are cast to the appropriate type because Postgres will assume `SELECT NULL` is `TEXT` by default and will refuse to
+  `UNION` two columns of two different types.)"
+  (ordered-map/ordered-map
+   ;; returned for all models. Important to be first for changing model for dataset
+   :model               :text
+   :id                  :integer
+   :name                :text
+   :display_name        :text
+   :description         :text
+   :archived            :boolean
+   ;; returned for Card, Dashboard, and Collection
+   :collection_id       :integer
+   :collection_name     :text
+   :collection_authority_level :text
+   ;; returned for Card and Dashboard
+   :collection_position :integer
+   :creator_id          :integer
+   :created_at          :timestamp
+   :bookmark            :boolean
+   ;; returned for everything except Collection
+   :updated_at          :timestamp
+   ;; returned for Card only, used for scoring
+   :dashboardcard_count :integer
+   :last_edited_at      :timestamp
+   :last_editor_id      :integer
+   :moderated_status    :text
+   ;; returned for Metric and Segment
+   :table_id            :integer
+   :table_schema        :text
+   :table_name          :text
+   :table_description   :text
+   ;; returned for Metric, Segment, and Action
+   :database_id         :integer
+   ;; returned for Database and Table
+   :initial_sync_status :text
+   ;; returned for Action
+   :model_id            :integer
+   :model_name          :text
+   ;; returned for indexed-entity
+   :pk_ref              :text
+   :model_index_id      :integer
+   ;; returned for Card and Action
+   :dataset_query       :text))
+
 (def ^:const displayed-columns
   "All of the result components that by default are displayed by the frontend."
   #{:name :display_name :collection_name :description})
@@ -73,11 +183,13 @@
 (defmethod searchable-columns-for-model "action"
   [_]
   [:name
+   :dataset_query
    :description])
 
 (defmethod searchable-columns-for-model "card"
   [_]
   [:name
+   :dataset_query
    :description])
 
 (defmethod searchable-columns-for-model "dataset"
@@ -110,7 +222,7 @@
 
 (def ^:private default-columns
   "Columns returned for all models."
-  [:id :name :description :archived :updated_at])
+  [:id :name :description :archived :created_at :updated_at])
 
 (def ^:private bookmark-col
   "Case statement to return boolean values of `:bookmark` for Card, Collection and Dashboard."
@@ -126,6 +238,7 @@
 (def ^:private table-columns
   "Columns containing information about the Table this model references. Returned for Metrics and Segments."
   [:table_id
+   :created_at
    [:table.db_id       :database_id]
    [:table.schema      :table_schema]
    [:table.name        :table_name]
@@ -139,14 +252,16 @@
 (defmethod columns-for-model "action"
   [_]
   (conj default-columns :model_id
+        :creator_id
         [:model.collection_id        :collection_id]
         [:model.id                   :model_id]
         [:model.name                 :model_name]
-        [:query_action.database_id   :database_id]))
+        [:query_action.database_id   :database_id]
+        [:query_action.dataset_query :dataset_query]))
 
 (defmethod columns-for-model "card"
   [_]
-  (conj default-columns :collection_id :collection_position
+  (conj default-columns :collection_id :collection_position :dataset_query :creator_id
         [:collection.name :collection_name]
         [:collection.authority_level :collection_authority_level]
         [{:select   [:status]
@@ -171,18 +286,17 @@
    [:model.collection_id        :collection_id]
    [:model.id                   :model_id]
    [:model.name                 :model_name]
-   [:model.database_id          :database_id]
-   [:model.dataset_query        :dataset_query]])
+   [:model.database_id          :database_id]])
 
 (defmethod columns-for-model "dashboard"
   [_]
-  (conj default-columns :collection_id :collection_position bookmark-col
+  (conj default-columns :collection_id :collection_position :creator_id bookmark-col
         [:collection.name :collection_name]
         [:collection.authority_level :collection_authority_level]))
 
 (defmethod columns-for-model "database"
   [_]
-  [:id :name :description :updated_at :initial_sync_status])
+  [:id :name :description :created_at :updated_at :initial_sync_status])
 
 (defmethod columns-for-model "collection"
   [_]
@@ -194,16 +308,17 @@
 
 (defmethod columns-for-model "segment"
   [_]
-  (into default-columns table-columns))
+  (concat default-columns table-columns [:creator_id]))
 
 (defmethod columns-for-model "metric"
   [_]
-  (into default-columns table-columns))
+  (concat default-columns table-columns [:creator_id]))
 
 (defmethod columns-for-model "table"
   [_]
   [:id
    :name
+   :created_at
    :display_name
    :description
    :updated_at
@@ -222,3 +337,10 @@
 (defmethod column->string :default
   [value _ _]
   value)
+
+(defmethod column->string [:card :dataset_query]
+  [value _ _]
+  (let [query (json/parse-string value true)]
+    (if (= "native" (:type query))
+      (-> query :native :query)
+      "")))
diff --git a/src/metabase/search/filter.clj b/src/metabase/search/filter.clj
new file mode 100644
index 0000000000000000000000000000000000000000..1fb2301656ae49f31314b12961a980a3b3e2ed39
--- /dev/null
+++ b/src/metabase/search/filter.clj
@@ -0,0 +1,308 @@
+(ns metabase.search.filter
+  "Namespace that defines the filters that are applied to the search results.
+
+  There are required filters and optional filters.
+  Archived is an required filters and is always applied, the reason because by default we want to hide archived/inactive entities.
+
+  But there are OPTIONAL FILTERS like :created-by, :created-at, when these filters are provided, the results will return only
+  results of models that have these filters.
+
+  The multi method for optional filters should have the default implementation to throw for unsupported models, and then each model
+  that supports the filter should define its own method for the filter."
+  (:require
+   [clojure.set :as set]
+   [clojure.string :as str]
+   [honey.sql.helpers :as sql.helpers]
+   [metabase.driver.common.parameters.dates :as params.dates]
+   [metabase.public-settings.premium-features :as premium-features]
+   [metabase.search.config :as search.config :refer [SearchableModel SearchContext]]
+   [metabase.search.util :as search.util]
+   [metabase.util.date-2 :as u.date]
+   [metabase.util.i18n :refer [tru]]
+   [metabase.util.malli :as mu])
+  (:import
+   (java.time LocalDate)))
+
+
+(def ^:private true-clause [:inline [:= 1 1]])
+(def ^:private false-clause [:inline [:= 0 1]])
+
+;; ------------------------------------------------------------------------------------------------;;
+;;                                         Required Filters                                         ;
+;; ------------------------------------------------------------------------------------------------;;
+
+(defmulti ^:private archived-clause
+  "Clause to filter by the archived status of the entity."
+  {:arglists '([model archived?])}
+  (fn [model _] model))
+
+(defmethod archived-clause :default
+  [model archived?]
+  [:= (search.config/column-with-model-alias model :archived) archived?])
+
+;; Databases can't be archived
+(defmethod archived-clause "database"
+  [_model archived?]
+  (if archived?
+    false-clause
+    true-clause))
+
+(defmethod archived-clause "indexed-entity"
+  [_model archived?]
+  (if-not archived?
+    true-clause
+    false-clause))
+
+;; Table has an `:active` flag, but no `:archived` flag; never return inactive Tables
+(defmethod archived-clause "table"
+  [model archived?]
+  (if archived?
+    false-clause ; No tables should appear in archive searches
+    [:and
+     [:= (search.config/column-with-model-alias model :active) true]
+     [:= (search.config/column-with-model-alias model :visibility_type) nil]]))
+
+(mu/defn ^:private search-string-clause-for-model
+  [model                :- SearchableModel
+   search-context       :- SearchContext
+   search-native-query? :- [:maybe :boolean]]
+  (when-let [query (:search-string search-context)]
+    (into
+     [:or]
+     (for [column           (cond->> (search.config/searchable-columns-for-model model)
+                              (not search-native-query?)
+                              (remove #{:dataset_query})
+
+                              true
+                              (map #(search.config/column-with-model-alias model %)))
+           wildcarded-token (->> (search.util/normalize query)
+                                 search.util/tokenize
+                                 (map search.util/wildcard-match))]
+       (cond
+        (and (= model "indexed-entity") (premium-features/sandboxed-or-impersonated-user?))
+        [:= 0 1]
+
+        (and (#{"card" "dataset"} model) (= column (search.config/column-with-model-alias model :dataset_query)))
+        [:and
+         [:= (search.config/column-with-model-alias model :query_type) "native"]
+         [:like [:lower column] wildcarded-token]]
+
+        (and (#{"action"} model)
+             (= column (search.config/column-with-model-alias model :dataset_query)))
+        [:like [:lower :query_action.dataset_query] wildcarded-token]
+
+        :else
+        [:like [:lower column] wildcarded-token])))))
+
+;; ------------------------------------------------------------------------------------------------;;
+;;                                         Optional filters                                        ;;
+;; ------------------------------------------------------------------------------------------------;;
+
+(defmulti ^:private build-optional-filter-query
+  "Build the query to filter by `filter`.
+  Dispath with an array of [filter model-name]."
+  {:arglists '([model fitler query filter-value])}
+  (fn [filter model _query _filter-value]
+    [filter model]))
+
+(defmethod build-optional-filter-query :default
+  [filter model _query _creator-id]
+  (throw (ex-info (format "%s filter for %s is not supported" filter model) {:filter filter :model model})))
+
+;; Created by filters
+(defn- default-created-by-fitler-clause
+  [model creator-ids]
+  (if (= 1 (count creator-ids))
+    [:= (search.config/column-with-model-alias model :creator_id) (first creator-ids)]
+    [:in (search.config/column-with-model-alias model :creator_id) creator-ids]))
+
+(doseq [model ["card" "dataset" "dashboard" "action"]]
+  (defmethod build-optional-filter-query [:created-by model]
+    [_filter model query creator-ids]
+    (sql.helpers/where query (default-created-by-fitler-clause model creator-ids))))
+
+;; Verified filters
+
+(defmethod build-optional-filter-query [:verified "card"]
+  [_filter model query verified]
+  (assert (true? verified) "filter for non-verified cards is not supported")
+  (if (premium-features/has-feature? :content-verification)
+    (-> query
+        (sql.helpers/join :moderation_review
+                          [:= :moderation_review.moderated_item_id
+                           (search.config/column-with-model-alias model :id)])
+        (sql.helpers/where [:= :moderation_review.status "verified"]
+                           [:= :moderation_review.moderated_item_type "card"]
+                           [:= :moderation_review.most_recent true]))
+    (sql.helpers/where query false-clause)))
+
+(defmethod build-optional-filter-query [:verified "dataset"]
+  [filter _model query verified]
+  (build-optional-filter-query filter "card" query verified))
+
+;; Created at filters
+
+(defn- date-range-filter-clause
+  [dt-col dt-val]
+  (let [date-range (try
+                    (params.dates/date-string->range dt-val {:inclusive-end? false})
+                    (catch Exception _e
+                      (throw (ex-info (tru "Failed to parse datetime value: {0}" dt-val) {:status-code 400}))))
+        start      (some-> (:start date-range) u.date/parse)
+        end        (some-> (:end date-range) u.date/parse)
+        dt-col     (if (some #(instance? LocalDate %) [start end])
+                     [:cast dt-col :date]
+                     dt-col)]
+    (cond
+     (= start end)
+     [:= dt-col start]
+
+     (nil? start)
+     [:< dt-col end]
+
+     (nil? end)
+     [:> dt-col start]
+
+     :else
+     [:and [:>= dt-col start] [:< dt-col end]])))
+
+(doseq [model ["collection" "database" "table" "dashboard" "card" "dataset" "action"]]
+  (defmethod build-optional-filter-query [:created-at model]
+    [_filter model query created-at]
+    (sql.helpers/where query (date-range-filter-clause
+                              (search.config/column-with-model-alias model :created_at)
+                              created-at))))
+
+;; Last edited by filter
+
+(defn- joined-with-table?
+  "Check if  the query have a join with `table`.
+  Note: this does a very shallow check by only checking if the join-clause is the same.
+  Using the same table with a different alias will return false.
+
+    (-> (sql.helpers/select :*)
+        (sql.helpers/from [:a])
+        (sql.helpers/join :b [:= :a.id :b.id])
+        (joined-with-table? :join :b))
+
+    ;; => true"
+  [query join-type table]
+  (->> (get query join-type) (partition 2) (map first) (some #(= % table)) boolean))
+
+(doseq [model ["dashboard" "card" "dataset" "metric"]]
+  (defmethod build-optional-filter-query [:last-edited-by model]
+    [_filter model query editor-ids]
+    (cond-> query
+      ;; both last-edited-by and last-edited-at join with revision, so we should be careful not to join twice
+      (not (joined-with-table? query :join :revision))
+      (-> (sql.helpers/join :revision [:= :revision.model_id (search.config/column-with-model-alias model :id)])
+          (sql.helpers/where [:= :revision.most_recent true]
+                             [:= :revision.model (search.config/search-model->revision-model model)]))
+      (= 1 (count editor-ids))
+      (sql.helpers/where [:= :revision.user_id (first editor-ids)])
+
+      (> (count editor-ids) 1)
+      (sql.helpers/where [:in :revision.user_id editor-ids]))))
+
+(doseq [model ["dashboard" "card" "dataset" "metric"]]
+  (defmethod build-optional-filter-query [:last-edited-at model]
+    [_filter model query last-edited-at]
+    (cond-> query
+      ;; both last-edited-by and last-edited-at join with revision, so we should be careful not to join twice
+      (not (joined-with-table? query :join :revision))
+      (-> (sql.helpers/join :revision [:= :revision.model_id (search.config/column-with-model-alias model :id)])
+          (sql.helpers/where [:= :revision.most_recent true]
+                             [:= :revision.model (search.config/search-model->revision-model model)]))
+      true
+      ;; on UI we showed the the last edit info from revision.timestamp
+      ;; not the model.updated_at column
+      ;; to be consistent we use revision.timestamp to do the filtering
+      (sql.helpers/where (date-range-filter-clause :revision.timestamp last-edited-at)))))
+
+;; TODO: once we record revision for actions, we should update this to use the same approach with dashboard/card
+(defmethod build-optional-filter-query [:last-edited-at "action"]
+  [_filter model query last-edited-at]
+  (sql.helpers/where query (date-range-filter-clause
+                              (search.config/column-with-model-alias model :updated_at)
+                              last-edited-at)))
+
+(defn- feature->supported-models
+  "Return A map of filter to its support models.
+
+  E.g: {:created-by #{\"card\" \"dataset\" \"dashboard\" \"action\"}}
+
+  This is function instead of a def so that optional-filter-clause can be defined anywhere in the codebase."
+  []
+  (merge
+   ;; models support search-native-query if dataset_query is one of the searchable columns
+   {:search-native-query (->> (dissoc (methods search.config/searchable-columns-for-model) :default)
+                              (filter (fn [[k v]]
+                                        (contains? (set (v k)) :dataset_query)))
+                              (map first)
+                              set)}
+   (->> (dissoc (methods build-optional-filter-query) :default)
+        keys
+        (reduce (fn [acc [filter model]]
+                  (update acc filter set/union #{model}))
+                {}))))
+
+;; ------------------------------------------------------------------------------------------------;;
+;;                                        Public functions                                         ;;
+;; ------------------------------------------------------------------------------------------------;;
+
+(mu/defn search-context->applicable-models :- [:set SearchableModel]
+  "Returns a set of models that are applicable given the search context.
+
+  If the context has optional filters, the models will be restricted for the set of supported models only."
+  [search-context :- SearchContext]
+  (let [{:keys [created-at
+                created-by
+                last-edited-at
+                last-edited-by
+                models
+                search-native-query
+                verified]}        search-context
+        feature->supported-models (feature->supported-models)]
+    (cond-> models
+      (some? created-at)          (set/intersection (:created-at feature->supported-models))
+      (some? created-by)          (set/intersection (:created-by feature->supported-models))
+      (some? last-edited-at)      (set/intersection (:last-edited-at feature->supported-models))
+      (some? last-edited-by)      (set/intersection (:last-edited-by feature->supported-models))
+      (true? search-native-query) (set/intersection (:search-native-query feature->supported-models))
+      (true? verified)            (set/intersection (:verified feature->supported-models)))))
+
+(mu/defn build-filters :- map?
+  "Build the search filters for a model."
+  [honeysql-query :- :map
+   model          :- SearchableModel
+   search-context :- SearchContext]
+  (let [{:keys [archived?
+                created-at
+                created-by
+                last-edited-at
+                last-edited-by
+                search-string
+                search-native-query
+                verified]}    search-context]
+    (cond-> honeysql-query
+      (not (str/blank? search-string))
+      (sql.helpers/where (search-string-clause-for-model model search-context search-native-query))
+
+      (some? archived?)
+      (sql.helpers/where (archived-clause model archived?))
+
+      ;; build optional filters
+      (some? created-at)
+      (#(build-optional-filter-query :created-at model % created-at))
+
+      (some? created-by)
+      (#(build-optional-filter-query :created-by model % created-by))
+
+      (some? last-edited-at)
+      (#(build-optional-filter-query :last-edited-at model % last-edited-at))
+
+      (some? last-edited-by)
+      (#(build-optional-filter-query :last-edited-by model % last-edited-by))
+
+      (some? verified)
+      (#(build-optional-filter-query :verified model % verified)))))
diff --git a/src/metabase/search/scoring.clj b/src/metabase/search/scoring.clj
index 3b7d662320b6ea565fcc75d90e9ff01273e4a035..ec6acc618d220133130cf62b7fa7c378a0aa56b8 100644
--- a/src/metabase/search/scoring.clj
+++ b/src/metabase/search/scoring.clj
@@ -111,11 +111,13 @@
 
   Some of the scorers can be tweaked with configuration in [[metabase.search.config]]."
   (:require
+   [cheshire.core :as json]
    [clojure.string :as str]
    [java-time.api :as t]
+   [metabase.mbql.normalize :as mbql.normalize]
    [metabase.public-settings.premium-features :refer [defenterprise]]
-   [metabase.search.config :as search-config]
-   [metabase.search.util :as search-util]
+   [metabase.search.config :as search.config]
+   [metabase.search.util :as search.util]
    [metabase.util :as u]))
 
 (defn- matches?
@@ -129,7 +131,7 @@
 (defn- tokens->string
   [tokens abbreviate?]
   (let [->string (partial str/join " ")
-        context  search-config/surrounding-match-context]
+        context  search.config/surrounding-match-context]
     (if (or (not abbreviate?)
             (<= (count tokens) (* 2 context)))
       (->string tokens)
@@ -158,13 +160,13 @@
   the text match, if there is one. If there is no match, the score is 0."
   [weighted-scorers query-tokens search-result]
   ;; TODO is pmap over search-result worth it?
-  (let [scores (for [column      (search-config/searchable-columns-for-model (:model search-result))
+  (let [scores (for [column      (search.config/searchable-columns-for-model (:model search-result))
                      {:keys [scorer name weight]
                       :as   _ws} weighted-scorers
                      :let        [matched-text (-> search-result
                                                    (get column)
-                                                   (search-config/column->string (:model search-result) column))
-                                  match-tokens (some-> matched-text search-util/normalize search-util/tokenize)
+                                                   (search.config/column->string (:model search-result) column))
+                                  match-tokens (some-> matched-text search.util/normalize search.util/tokenize)
                                   raw-score (scorer query-tokens match-tokens)]
                      :when       (and matched-text (pos? raw-score))]
                  {:score               raw-score
@@ -179,7 +181,7 @@
 
 (defn- consecutivity-scorer
   [query-tokens match-tokens]
-  (/ (search-util/largest-common-subseq-length
+  (/ (search.util/largest-common-subseq-length
       matches?
       ;; See comment on largest-common-subseq-length re. its cache. This is a little conservative, but better to under- than over-estimate
       (take 30 query-tokens)
@@ -248,7 +250,7 @@
    {:scorer prefix-scorer :name "prefix" :weight 1}])
 
 (def ^:private model->sort-position
-  (zipmap (reverse search-config/all-models) (range)))
+  (zipmap (reverse search.config/models-search-order) (range)))
 
 (defn- model-score
   [{:keys [model]}]
@@ -259,7 +261,7 @@
   [raw-search-string result]
   (if (seq raw-search-string)
     (text-scores-with match-based-scorers
-                      (search-util/tokenize (search-util/normalize raw-search-string))
+                      (search.util/tokenize (search.util/normalize raw-search-string))
                       result)
     [{:score 0 :weight 0}]))
 
@@ -283,13 +285,13 @@
   [{:keys [model dashboardcard_count]}]
   (if (= model "card")
     (min (/ dashboardcard_count
-            search-config/dashboard-count-ceiling)
+            search.config/dashboard-count-ceiling)
          1)
     0))
 
 (defn- recency-score
   [{:keys [updated_at]}]
-  (let [stale-time search-config/stale-time-in-days
+  (let [stale-time search.config/stale-time-in-days
         days-ago (if updated_at
                    (t/time-between updated_at
                                    (t/offset-date-time)
@@ -312,12 +314,13 @@
                            name)
          :context        (when (and match-context-thunk
                                     (empty?
-                                     (remove matching-columns search-config/displayed-columns)))
+                                     (remove matching-columns search.config/displayed-columns)))
                            (match-context-thunk))
          :collection     {:id              collection_id
                           :name            collection_name
                           :authority_level collection_authority_level}
          :scores          all-scores)
+        (update :dataset_query #(some-> % json/parse-string mbql.normalize/normalize))
         (dissoc
          :collection_id
          :collection_name
diff --git a/src/metabase/search/util.clj b/src/metabase/search/util.clj
index 235772c7da474a2f6f436474945c564a606bd6a5..a1954f0d8b8e721137ad278b7990ce8d95b447fe 100644
--- a/src/metabase/search/util.clj
+++ b/src/metabase/search/util.clj
@@ -5,6 +5,11 @@
    [metabase.util :as u]
    [metabase.util.malli :as mu]))
 
+(defn wildcard-match
+  "Returns a string pattern to match a wildcard search term."
+  [s]
+  (str "%" s "%"))
+
 (mu/defn normalize :- :string
   "Normalize a `query` to lower-case."
   [query :- :string]
diff --git a/src/metabase/upload.clj b/src/metabase/upload.clj
index 8cc18a0a12bac0738cb3741805dca1edd9a168b7..0875c66385826cf0a9fb365bc8edec971389e938 100644
--- a/src/metabase/upload.clj
+++ b/src/metabase/upload.clj
@@ -11,7 +11,7 @@
    [metabase.driver :as driver]
    [metabase.mbql.util :as mbql.u]
    [metabase.public-settings :as public-settings]
-   [metabase.search.util :as search-util]
+   [metabase.search.util :as search.util]
    [metabase.util :as u]
    [metabase.util.i18n :refer [tru]])
   (:import
@@ -154,7 +154,7 @@
 
 (defn- row->types
   [row]
-  (map (comp value->type search-util/normalize) row))
+  (map (comp value->type search.util/normalize) row))
 
 (defn- lowest-common-member [[x & xs :as all-xs] ys]
   (cond
diff --git a/src/metabase/util/date_2.clj b/src/metabase/util/date_2.clj
index 3ccd6329a8bd595fdd265932e952011062b8339e..23b35bc8ca1c22cf867b8267b1980f7e55556123 100644
--- a/src/metabase/util/date_2.clj
+++ b/src/metabase/util/date_2.clj
@@ -162,7 +162,8 @@
                             (some-> t class .getCanonicalName))
                        {:t t}))))))
 
-(def ^:private add-units
+(def add-units
+  "A list of units that can be added to a temporal value."
   #{:millisecond :second :minute :hour :day :week :month :quarter :year})
 
 (s/defn add :- Temporal
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index 4efaee46f513e1ee03cfb0a70aa2f54a3b0ce1c9..4f40d4714356de7ca919c9c0015b9ecd30a6e7d1 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -39,10 +39,8 @@
    [metabase.models.moderation-review :as moderation-review]
    [metabase.models.permissions :as perms]
    [metabase.models.permissions-group :as perms-group]
-   [metabase.models.revision :as revision :refer [Revision]]
-   [metabase.models.user :refer [User]]
-   [metabase.public-settings.premium-features-test
-    :as premium-features-test]
+   [metabase.models.revision :as revision]
+   [metabase.public-settings.premium-features-test :as premium-features-test]
    [metabase.query-processor :as qp]
    [metabase.query-processor.async :as qp.async]
    [metabase.query-processor.card :as qp.card]
@@ -398,6 +396,41 @@
                (into #{} (map :name) (mt/user-http-request :rasta :get 200 "card"
                                                            :f :using_model :model_id model-id))))))))
 
+(deftest get-cards-with-last-edit-info-test
+  (mt/with-temp [:model/Card {card-1-id :id} {:name "Card 1"}
+                 :model/Card {card-2-id :id} {:name "Card 2"}]
+    (with-cards-in-readable-collection [card-1-id card-2-id]
+      (doseq [user-id [(mt/user->id :rasta) (mt/user->id :crowberto)]]
+        (revision/push-revision!
+         :entity      :model/Card
+         :id          card-1-id
+         :user-id     user-id
+         :is_creation true
+         :object      {:id card-1-id}))
+
+      (doseq [user-id [(mt/user->id :crowberto) (mt/user->id :rasta)]]
+        (revision/push-revision!
+         :entity      :model/Card
+         :id          card-2-id
+         :user-id     user-id
+         :is_creation true
+         :object      {:id card-2-id}))
+      (let [results (m/index-by :id (mt/user-http-request :rasta :get 200 "card"))]
+        (is (=? {:name           "Card 1"
+                 :last-edit-info {:id         (mt/user->id :rasta)
+                                  :email      "rasta@metabase.com"
+                                  :first_name "Rasta"
+                                  :last_name  "Toucan"
+                                  :timestamp  some?}}
+                (get results card-1-id)))
+        (is (=? {:name           "Card 2"
+                 :last-edit-info {:id         (mt/user->id :crowberto)
+                                  :email      "crowberto@metabase.com"
+                                  :first_name "Crowberto"
+                                  :last_name  "Corv"
+                                  :timestamp  some?}}
+                (get results card-2-id)))))))
+
 (deftest get-series-for-card-permission-test
   (t2.with-temp/with-temp
     [:model/Card {card-id :id} {:name          "Card"
@@ -1062,11 +1095,11 @@
                    :metabase_version       config/mb-version-string})
                  (mt/user-http-request :rasta :get 200 (str "card/" (u/the-id card))))))
         (testing "Card should include last edit info if available"
-          (mt/with-temp [User     {user-id :id} {:first_name "Test" :last_name "User" :email "user@test.com"}
-                         Revision _ {:model    "Card"
-                                     :model_id (:id card)
-                                     :user_id  user-id
-                                     :object   (revision/serialize-instance card (:id card) card)}]
+          (mt/with-temp [:model/User     {user-id :id} {:first_name "Test" :last_name "User" :email "user@test.com"}
+                         :model/Revision _             {:model    "Card"
+                                                        :model_id (:id card)
+                                                        :user_id  user-id
+                                                        :object   (revision/serialize-instance card (:id card) card)}]
             (is (= {:id true :email "user@test.com" :first_name "Test" :last_name "User" :timestamp true}
                    (-> (mt/user-http-request :rasta :get 200 (str "card/" (u/the-id card)))
                        mt/boolean-ids-and-timestamps
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index bc29fa1384fe9a813c527e2eeefe4f2e15b96e9d..469efabe19c725ec2bd1ef4fbcc69f1c4cff7675 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -30,6 +30,7 @@
             Revision
             Table
             User]]
+   [metabase.models.collection :as collection]
    [metabase.models.dashboard :as dashboard]
    [metabase.models.dashboard-card :as dashboard-card]
    [metabase.models.dashboard-test :as dashboard-test]
@@ -215,7 +216,7 @@
       (t2.with-temp/with-temp [Collection collection]
         (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
         (let [test-dashboard-name "Test Create Dashboard"]
-          (try
+          (mt/with-model-cleanup [:model/Dashboard]
             (is (= (merge
                     dashboard-defaults
                     {:name           test-dashboard-name
@@ -232,36 +233,85 @@
                                                                            :parameters    [{:id "abc123", :name "test", :type "date"}]
                                                                            :cache_ttl     1234
                                                                            :collection_id (u/the-id collection)})
-                       dashboard-response)))
-            (finally
-              (t2/delete! Dashboard :name test-dashboard-name))))))))
+                       dashboard-response)))))))))
 
 (deftest create-dashboard-with-collection-position-test
   (testing "POST /api/dashboard"
     (testing "Make sure we can create a Dashboard with a Collection position"
       (mt/with-non-admin-groups-no-root-collection-perms
         (t2.with-temp/with-temp [Collection collection]
-          (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-          (let [dashboard-name (mt/random-name)]
-            (try
+          (mt/with-model-cleanup [:model/Dashboard]
+            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+            (let [dashboard-name (mt/random-name)]
               (mt/user-http-request :rasta :post 200 "dashboard" {:name                dashboard-name
                                                                   :collection_id       (u/the-id collection)
                                                                   :collection_position 1000})
               (is (=? {:collection_id true, :collection_position 1000}
                       (some-> (t2/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)
-                              (update :collection_id (partial = (u/the-id collection))))))
-              (finally
-                (t2/delete! Dashboard :name dashboard-name)))))
+                              (update :collection_id (partial = (u/the-id collection))))))))
 
-        (testing "..but not if we don't have permissions for the Collection"
-          (t2.with-temp/with-temp [Collection collection]
-            (let [dashboard-name (mt/random-name)]
-              (mt/user-http-request :rasta :post 403 "dashboard" {:name                dashboard-name
-                                                                  :collection_id       (u/the-id collection)
-                                                                  :collection_position 1000})
-              (is (= nil
-                     (some-> (t2/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)
-                             (update :collection_id (partial = (u/the-id collection)))))))))))))
+          (testing "..but not if we don't have permissions for the Collection"
+            (t2.with-temp/with-temp [Collection collection]
+              (let [dashboard-name (mt/random-name)]
+                (mt/user-http-request :rasta :post 403 "dashboard" {:name                dashboard-name
+                                                                    :collection_id       (u/the-id collection)
+                                                                    :collection_position 1000})
+                (is (not (t2/select-one [Dashboard :collection_id :collection_position] :name dashboard-name)))))))))))
+
+;;; +----------------------------------------------------------------------------------------------------------------+
+;;; |                                               GET /api/dashboard/                                              |
+;;; +----------------------------------------------------------------------------------------------------------------+
+
+(deftest get-dashboards-test
+  (mt/with-temp
+    [:model/Dashboard {rasta-dash     :id} {:creator_id    (mt/user->id :rasta)}
+     :model/Dashboard {crowberto-dash :id} {:creator_id    (mt/user->id :crowberto)
+                                            :collection_id (:id (collection/user->personal-collection (mt/user->id :crowberto)))}
+     :model/Dashboard {archived-dash  :id} {:archived      true
+                                            :collection_id (:id (collection/user->personal-collection (mt/user->id :crowberto)))
+                                            :creator_id    (mt/user->id :crowberto)}]
+
+    (testing "should include creator info and last edited info"
+      (revision/push-revision!
+       :entity      :model/Dashboard
+       :id          crowberto-dash
+       :user-id     (mt/user->id :crowberto)
+       :is_creation true
+       :object      {:id crowberto-dash})
+      (is (=? (merge (t2/select-one :model/Dashboard crowberto-dash)
+                     {:creator        {:id          (mt/user->id :crowberto)
+                                       :email       "crowberto@metabase.com"
+                                       :first_name  "Crowberto"
+                                       :last_name   "Corv"
+                                       :common_name "Crowberto Corv"}}
+                     {:last-edit-info {:id         (mt/user->id :crowberto)
+                                       :first_name "Crowberto"
+                                       :last_name  "Corv"
+                                       :email      "crowberto@metabase.com"
+                                       :timestamp  true}})
+              (-> (mt/user-http-request :crowberto :get 200 "dashboard" :f "mine")
+                  first
+                  (update-in [:last-edit-info :timestamp] boolean)))))
+
+    (testing "f=all shouldn't return archived dashboards"
+      (is (= #{rasta-dash crowberto-dash}
+             (set (map :id (mt/user-http-request :crowberto :get 200 "dashboard" :f "all")))))
+
+      (testing "and should respect read perms"
+        (is (= #{rasta-dash}
+               (set (map :id (mt/user-http-request :rasta :get 200 "dashboard" :f "all")))))))
+
+    (testing "f=archvied return archived dashboards"
+      (is (= #{archived-dash}
+             (set (map :id (mt/user-http-request :crowberto :get 200 "dashboard" :f "archived")))))
+
+      (testing "and should return read perms"
+        (is (= #{}
+               (set (map :id (mt/user-http-request :rasta :get 200 "dashboard" :f "archived")))))))
+
+    (testing "f=mine return dashboards created by caller but do not include archived"
+      (is (= #{crowberto-dash}
+             (set (map :id (mt/user-http-request :crowberto :get 200 "dashboard" :f "mine"))))))))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                             GET /api/dashboard/:id                                             |
@@ -819,15 +869,15 @@
     (testing "Check that adding a new Dashboard at Collection position 3 will increment position of the existing item at position 3"
       (mt/with-non-admin-groups-no-root-collection-perms
         (t2.with-temp/with-temp [Collection collection]
-          (api.card-test/with-ordered-items collection [Card  a
-                                                        Pulse b
-                                                        Card  d]
-            (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
-            (is (= {"a" 1
-                    "b" 2
-                    "d" 3}
-                   (api.card-test/get-name->collection-position :rasta collection)))
-            (try
+          (mt/with-model-cleanup [:model/Dashboard]
+            (api.card-test/with-ordered-items collection [Card  a
+                                                          Pulse b
+                                                          Card  d]
+              (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection)
+              (is (= {"a" 1
+                      "b" 2
+                      "d" 3}
+                     (api.card-test/get-name->collection-position :rasta collection)))
               (mt/user-http-request :rasta :post 200 "dashboard" {:name                "c"
                                                                   :collection_id       (u/the-id collection)
                                                                   :collection_position 3})
@@ -835,9 +885,7 @@
                       "b" 2
                       "c" 3
                       "d" 4}
-                     (api.card-test/get-name->collection-position :rasta collection)))
-              (finally
-                (t2/delete! Dashboard :collection_id (u/the-id collection))))))))))
+                     (api.card-test/get-name->collection-position :rasta collection))))))))))
 
 (deftest insert-dashboard-no-position-test
   (testing "POST /api/dashboard"
@@ -852,17 +900,14 @@
                     "b" 2
                     "d" 3}
                    (api.card-test/get-name->collection-position :rasta collection)))
-            (try
+            (mt/with-model-cleanup [:model/Dashboard]
               (mt/user-http-request :rasta :post 200 "dashboard" {:name          "c"
                                                                   :collection_id (u/the-id collection)})
               (is (= {"a" 1
                       "b" 2
                       "c" nil
                       "d" 3}
-                     (api.card-test/get-name->collection-position :rasta collection)))
-              (finally
-                (t2/delete! Dashboard :collection_id (u/the-id collection))))))))))
-
+                     (api.card-test/get-name->collection-position :rasta collection))))))))))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                           DELETE /api/dashboard/:id                                            |
@@ -881,13 +926,13 @@
 ;;; +----------------------------------------------------------------------------------------------------------------+
 
 (deftest copy-dashboard-test
-  (testing "POST /api/dashboard/:id/copy"
-    (testing "A plain copy with nothing special"
-      (t2.with-temp/with-temp [Dashboard dashboard {:name        "Test Dashboard"
-                                                    :description "A description"
-                                                    :creator_id  (mt/user->id :rasta)}]
-        (let [response (mt/user-http-request :rasta :post 200 (format "dashboard/%d/copy" (:id dashboard)))]
-          (try
+  (mt/with-model-cleanup [:model/Dashboard]
+    (testing "POST /api/dashboard/:id/copy"
+      (testing "A plain copy with nothing special"
+        (t2.with-temp/with-temp [Dashboard dashboard {:name        "Test Dashboard"
+                                                      :description "A description"
+                                                      :creator_id  (mt/user->id :rasta)}]
+          (let [response (mt/user-http-request :rasta :post 200 (format "dashboard/%d/copy" (:id dashboard)))]
             (is (= (merge
                     dashboard-defaults
                     {:name          "Test Dashboard"
@@ -898,19 +943,18 @@
                    (dashboard-response response)))
             (is (some? (:entity_id response)))
             (is (not=  (:entity_id dashboard) (:entity_id response))
-                "The copy should have a new entity ID generated")
-            (finally
-              (t2/delete! Dashboard :id (u/the-id response)))))))))
+                "The copy should have a new entity ID generated")))))))
+
 
 (deftest copy-dashboard-test-2
-  (testing "POST /api/dashboard/:id/copy"
-    (testing "Ensure name / description / user set when copying"
-      (t2.with-temp/with-temp [Dashboard dashboard  {:name        "Test Dashboard"
-                                                     :description "An old description"}]
-        (let [response (mt/user-http-request :crowberto :post 200 (format "dashboard/%d/copy" (:id dashboard))
-                                             {:name        "Test Dashboard - Duplicate"
-                                              :description "A new description"})]
-          (try
+  (mt/with-model-cleanup [:model/Dashboard]
+    (testing "POST /api/dashboard/:id/copy"
+      (testing "Ensure name / description / user set when copying"
+        (t2.with-temp/with-temp [Dashboard dashboard  {:name        "Test Dashboard"
+                                                       :description "An old description"}]
+          (let [response (mt/user-http-request :crowberto :post 200 (format "dashboard/%d/copy" (:id dashboard))
+                                               {:name        "Test Dashboard - Duplicate"
+                                                :description "A new description"})]
             (is (= (merge
                     dashboard-defaults
                     {:name          "Test Dashboard - Duplicate"
@@ -921,68 +965,68 @@
                    (dashboard-response response)))
             (is (some? (:entity_id response)))
             (is (not= (:entity_id dashboard) (:entity_id response))
-                "The copy should have a new entity ID generated")
-            (finally
-              (t2/delete! Dashboard :id (u/the-id response)))))))))
+                "The copy should have a new entity ID generated")))))))
+
 
 (deftest copy-dashboard-test-3
   (testing "Deep copy: POST /api/dashboard/:id/copy"
     (mt/dataset sample-dataset
-      (mt/with-temp [Collection source-coll {:name "Source collection"}
-                     Collection dest-coll   {:name "Destination collection"}
-                     Dashboard  dashboard {:name          "Dashboard to be Copied"
-                                           :description   "A description"
-                                           :collection_id (u/the-id source-coll)
-                                           :creator_id    (mt/user->id :rasta)}
-                     Card       total-card  {:name "Total orders per month"
-                                             :collection_id (u/the-id source-coll)
-                                             :display :line
-                                             :visualization_settings
-                                             {:graph.dimensions ["CREATED_AT"]
-                                              :graph.metrics ["sum"]}
-                                             :dataset_query
-                                             (mt/$ids
-                                              {:database (mt/id)
-                                               :type     :query
-                                               :query    {:source-table $$orders
-                                                          :aggregation  [[:sum $orders.total]]
-                                                          :breakout     [!month.orders.created_at]}})}
-                     Card      avg-card  {:name "Average orders per month"
-                                          :collection_id (u/the-id source-coll)
-                                          :display :line
-                                          :visualization_settings
-                                          {:graph.dimensions ["CREATED_AT"]
-                                           :graph.metrics ["sum"]}
-                                          :dataset_query
-                                          (mt/$ids
-                                           {:database (mt/id)
-                                            :type     :query
-                                            :query    {:source-table $$orders
-                                                       :aggregation  [[:avg $orders.total]]
-                                                       :breakout     [!month.orders.created_at]}})}
-                     Card          model {:name "A model"
-                                          :collection_id (u/the-id source-coll)
-                                          :dataset true
-                                          :dataset_query
-                                          (mt/$ids
-                                           {:database (mt/id)
-                                            :type :query
-                                            :query {:source-table $$orders
-                                                    :limit 4}})}
-                     DashboardCard dashcard {:dashboard_id (u/the-id dashboard)
-                                             :card_id    (u/the-id total-card)
-                                             :size_x 6, :size_y 6}
-                     DashboardCard _textcard {:dashboard_id (u/the-id dashboard)
-                                              :visualization_settings
-                                              {:virtual_card
-                                               {:display :text}
-                                               :text "here is some text"}}
-                     DashboardCard _        {:dashboard_id (u/the-id dashboard)
-                                             :card_id    (u/the-id model)
-                                             :size_x 6, :size_y 6}
-                     DashboardCardSeries _ {:dashboardcard_id (u/the-id dashcard)
-                                            :card_id (u/the-id avg-card)
-                                            :position 0}]
+      (mt/with-temp
+        [Collection source-coll {:name "Source collection"}
+         Collection dest-coll   {:name "Destination collection"}
+         Dashboard  dashboard {:name          "Dashboard to be Copied"
+                               :description   "A description"
+                               :collection_id (u/the-id source-coll)
+                               :creator_id    (mt/user->id :rasta)}
+         Card       total-card  {:name "Total orders per month"
+                                 :collection_id (u/the-id source-coll)
+                                 :display :line
+                                 :visualization_settings
+                                 {:graph.dimensions ["CREATED_AT"]
+                                  :graph.metrics ["sum"]}
+                                 :dataset_query
+                                 (mt/$ids
+                                  {:database (mt/id)
+                                   :type     :query
+                                   :query    {:source-table $$orders
+                                              :aggregation  [[:sum $orders.total]]
+                                              :breakout     [!month.orders.created_at]}})}
+         Card      avg-card  {:name "Average orders per month"
+                              :collection_id (u/the-id source-coll)
+                              :display :line
+                              :visualization_settings
+                              {:graph.dimensions ["CREATED_AT"]
+                               :graph.metrics ["sum"]}
+                              :dataset_query
+                              (mt/$ids
+                               {:database (mt/id)
+                                :type     :query
+                                :query    {:source-table $$orders
+                                           :aggregation  [[:avg $orders.total]]
+                                           :breakout     [!month.orders.created_at]}})}
+         Card          model {:name "A model"
+                              :collection_id (u/the-id source-coll)
+                              :dataset true
+                              :dataset_query
+                              (mt/$ids
+                               {:database (mt/id)
+                                :type :query
+                                :query {:source-table $$orders
+                                        :limit 4}})}
+         DashboardCard dashcard {:dashboard_id (u/the-id dashboard)
+                                 :card_id    (u/the-id total-card)
+                                 :size_x 6, :size_y 6}
+         DashboardCard _textcard {:dashboard_id (u/the-id dashboard)
+                                  :visualization_settings
+                                  {:virtual_card
+                                   {:display :text}
+                                   :text "here is some text"}}
+         DashboardCard _        {:dashboard_id (u/the-id dashboard)
+                                 :card_id    (u/the-id model)
+                                 :size_x 6, :size_y 6}
+         DashboardCardSeries _ {:dashboardcard_id (u/the-id dashcard)
+                                :card_id (u/the-id avg-card)
+                                :position 0}]
         (mt/with-model-cleanup [Card Dashboard DashboardCard DashboardCardSeries]
           (let [resp (mt/user-http-request :crowberto :post 200
                                            (format "dashboard/%d/copy" (:id dashboard))
@@ -1170,27 +1214,27 @@
   (testing "POST /api/dashboard/:id/copy"
     (testing "for a dashboard that has tabs"
       (with-simple-dashboard-with-tabs [{:keys [dashboard-id]}]
-        (let [new-dash-id        (:id (mt/user-http-request :rasta :post 200
-                                                            (format "dashboard/%d/copy" dashboard-id)
-                                                            {:name        "New dashboard"
-                                                             :description "A new description"}))
-              original-tabs      (t2/select [:model/DashboardTab :id :position :name]
-                                            :dashboard_id dashboard-id
-                                            {:order-by [[:position :asc]]})
-              new-tabs           (t2/select [:model/DashboardTab :id :position :name]
-                                            :dashboard_id new-dash-id
-                                            {:order-by [[:position :asc]]})
-              new->old-tab-id   (zipmap (map :id new-tabs) (map :id original-tabs))]
-         (testing "Cards are located correctly between tabs"
-           (is (= (map #(select-keys % [:dashboard_tab_id :card_id :row :col :size_x :size_y :dashboard_tab_id])
-                       (dashcards-by-position dashboard-id))
-                  (map #(select-keys % [:dashboard_tab_id :card_id :row :col :size_x :size_y :dashboard_tab_id])
-                       (for [card (dashcards-by-position new-dash-id)]
-                         (assoc card :dashboard_tab_id (new->old-tab-id (:dashboard_tab_id card))))))))
-
-         (testing "new tabs should have the same name and position"
-           (is (= (map #(dissoc % :id) original-tabs)
-                  (map #(dissoc % :id) new-tabs)))))))))
+        (mt/with-model-cleanup [:model/Dashboard]
+          (let [new-dash-id        (:id (mt/user-http-request :rasta :post 200
+                                                              (format "dashboard/%d/copy" dashboard-id)
+                                                              {:name        "New dashboard"
+                                                               :description "A new description"}))
+                original-tabs      (t2/select [:model/DashboardTab :id :position :name]
+                                              :dashboard_id dashboard-id
+                                              {:order-by [[:position :asc]]})
+                new-tabs           (t2/select [:model/DashboardTab :id :position :name]
+                                              :dashboard_id new-dash-id
+                                              {:order-by [[:position :asc]]})
+                new->old-tab-id   (zipmap (map :id new-tabs) (map :id original-tabs))]
+            (testing "Cards are located correctly between tabs"
+              (is (= (map #(select-keys % [:dashboard_tab_id :card_id :row :col :size_x :size_y :dashboard_tab_id])
+                          (dashcards-by-position dashboard-id))
+                     (map #(select-keys % [:dashboard_tab_id :card_id :row :col :size_x :size_y :dashboard_tab_id])
+                          (for [card (dashcards-by-position new-dash-id)]
+                            (assoc card :dashboard_tab_id (new->old-tab-id (:dashboard_tab_id card))))))))
+            (testing "new tabs should have the same name and position"
+              (is (= (map #(dissoc % :id) original-tabs)
+                     (map #(dissoc % :id) new-tabs))))))))))
 
 (def ^:dynamic ^:private
   ^{:doc "Set of ids that will report [[mi/can-write]] as true."}
@@ -1214,7 +1258,7 @@
 (deftest cards-to-copy-test
   (testing "Identifies all cards to be copied"
     (let [dashcards [{:card_id 1 :card (card-model {:id 1}) :series [(card-model {:id 2})]}
-                         {:card_id 3 :card (card-model{:id 3})}]]
+                     {:card_id 3 :card (card-model{:id 3})}]]
       (binding [*readable-card-ids* #{1 2 3}]
         (is (= {:copy {1 {:id 1} 2 {:id 2} 3 {:id 3}}
                 :discard []}
@@ -1222,14 +1266,14 @@
   (testing "Identifies cards which cannot be copied"
     (testing "If they are in a series"
       (let [dashcards [{:card_id 1 :card (card-model {:id 1}) :series [(card-model {:id 2})]}
-                           {:card_id 3 :card (card-model{:id 3})}]]
+                       {:card_id 3 :card (card-model{:id 3})}]]
         (binding [*readable-card-ids* #{1 3}]
           (is (= {:copy {1 {:id 1} 3 {:id 3}}
                   :discard [{:id 2}]}
                  (#'api.dashboard/cards-to-copy dashcards))))))
     (testing "When the base of a series lacks permissions"
       (let [dashcards [{:card_id 1 :card (card-model {:id 1}) :series [(card-model {:id 2})]}
-                           {:card_id 3 :card (card-model{:id 3})}]]
+                       {:card_id 3 :card (card-model{:id 3})}]]
         (binding [*readable-card-ids* #{3}]
           (is (= {:copy {3 {:id 3}}
                   :discard [{:id 1} {:id 2}]}
@@ -1238,7 +1282,7 @@
 (deftest update-cards-for-copy-test
   (testing "When copy style is shallow returns original dashcards"
     (let [dashcards [{:card_id 1 :card {:id 1} :series [{:id 2}]}
-                         {:card_id 3 :card {:id 3}}]]
+                     {:card_id 3 :card {:id 3}}]]
       (is (= dashcards
              (api.dashboard/update-cards-for-copy 1
                                                   dashcards
@@ -1267,7 +1311,7 @@
                                                     nil)))))
     (testing "Can omit whole card with series if not copied"
       (let [dashcards [{:card_id 1 :card {} :series [{:id 2} {:id 3}]}
-                           {:card_id 4 :card {} :series [{:id 5} {:id 6}]}]]
+                       {:card_id 4 :card {} :series [{:id 5} {:id 6}]}]]
         (is (= [{:card_id 7 :card {:id 7} :series [{:id 8} {:id 9}]}]
                (api.dashboard/update-cards-for-copy 1
                                                     dashcards
@@ -2147,32 +2191,31 @@
                                                            :card_id 123
                                                            :series  [8 9]}]}
                                 :message  "updated"}]
-      (is (= [{:is_reversion          false
-               :is_creation           false
-               :message               "updated"
-               :user                  (-> (user-details (mt/fetch-user :crowberto))
+      (is (=? [{:is_reversion          false
+                :is_creation           false
+                :message               "updated"
+                :user                  (-> (user-details (mt/fetch-user :crowberto))
+                                           (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
+                :metabase_version      config/mb-version-string
+                :diff                  {:before {:name        "b"
+                                                 :description nil
+                                                 :cards       [{:series nil, :size_y 4, :size_x 4}]}
+                                        :after  {:name        "c"
+                                                 :description "something"
+                                                 :cards       [{:series [8 9], :size_y 3, :size_x 5}]}}
+                :has_multiple_changes true
+                :description          "added a description and renamed it from \"b\" to \"c\", modified the cards and added some series to card 123."}
+               {:is_reversion         false
+                :is_creation          true
+                :message              nil
+                :user                 (-> (user-details (mt/fetch-user :rasta))
                                           (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
-               :metabase_version      config/mb-version-string
-               :diff                  {:before {:name        "b"
-                                                :description nil
-                                                :cards       [{:series nil, :size_y 4, :size_x 4}]}
-                                       :after  {:name        "c"
-                                                :description "something"
-                                                :cards       [{:series [8 9], :size_y 3, :size_x 5}]}}
-               :has_multiple_changes true
-               :description          "added a description and renamed it from \"b\" to \"c\", modified the cards and added some series to card 123."}
-              {:is_reversion         false
-               :is_creation          true
-               :message              nil
-               :metabase_version     config/mb-version-string
-               :user                 (-> (user-details (mt/fetch-user :rasta))
-                                         (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
-               :diff                 nil
-               :has_multiple_changes false
-               :description          "created this."}]
-             (doall (for [revision (mt/user-http-request :crowberto :get 200 (format "dashboard/%d/revisions" dashboard-id))]
-                      (dissoc revision :timestamp :id))))))))
-
+                :metabase_version      config/mb-version-string
+                :diff                 nil
+                :has_multiple_changes false
+                :description          "created this."}]
+              (doall (for [revision (mt/user-http-request :crowberto :get 200 (format "dashboard/%d/revisions" dashboard-id))]
+                       (dissoc revision :timestamp :id))))))))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                         POST /api/dashboard/:id/revert                                         |
@@ -2199,22 +2242,7 @@
                                                            :description nil
                                                            :cards       []}
                                                 :message  "updated"}]
-      (is (= {:is_reversion         true
-              :is_creation          false
-              :message              nil
-              :user                 (-> (user-details (mt/fetch-user :crowberto))
-                                        (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
-              :metabase_version     config/mb-version-string
-              :diff                 {:before {:name "b"}
-                                     :after  {:name "a"}}
-              :has_multiple_changes false
-              :description          "reverted to an earlier version."}
-             (dissoc (mt/user-http-request :crowberto :post 200 (format "dashboard/%d/revert" dashboard-id)
-                                           {:revision_id revision-id})
-                     :id :timestamp)))
-
-      (is (= [{:is_reversion         true
-               :is_creation          false
+      (is (=? {:is_reversion         true
                :message              nil
                :user                 (-> (user-details (mt/fetch-user :crowberto))
                                          (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
@@ -2223,27 +2251,39 @@
                                       :after  {:name "a"}}
                :has_multiple_changes false
                :description          "reverted to an earlier version."}
-              {:is_reversion         false
-               :is_creation          false
-               :message              "updated"
-               :metabase_version     config/mb-version-string
-               :user                 (-> (user-details (mt/fetch-user :crowberto))
-                                         (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
-               :diff                 {:before {:name "a"}
-                                      :after  {:name "b"}}
-               :has_multiple_changes false
-               :description          "renamed this Dashboard from \"a\" to \"b\"."}
-              {:is_reversion         false
-               :is_creation          true
-               :message              nil
-               :metabase_version     config/mb-version-string
-               :user                 (-> (user-details (mt/fetch-user :rasta))
-                                         (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
-               :diff                 nil
-               :has_multiple_changes false
-               :description          "created this."}]
-             (doall (for [revision (mt/user-http-request :crowberto :get 200 (format "dashboard/%d/revisions" dashboard-id))]
-                      (dissoc revision :timestamp :id))))))))
+              (dissoc (mt/user-http-request :crowberto :post 200 (format "dashboard/%d/revert" dashboard-id)
+                                            {:revision_id revision-id})
+                     :id :timestamp)))
+      (is (=? [{:is_reversion         true
+                :is_creation          false
+                :message              nil
+                :user                 (-> (user-details (mt/fetch-user :crowberto))
+                                          (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
+                :metabase_version     config/mb-version-string
+                :diff                 {:before {:name "b"}
+                                       :after  {:name "a"}}
+                :has_multiple_changes false
+                :description          "reverted to an earlier version."}
+               {:is_reversion         false
+                :is_creation          false
+                :message              "updated"
+                :user                 (-> (user-details (mt/fetch-user :crowberto))
+                                          (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
+                :metabase_version     config/mb-version-string
+                :diff                 {:before {:name "a"}
+                                       :after  {:name "b"}}
+                :has_multiple_changes false
+                :description          "renamed this Dashboard from \"a\" to \"b\"."}
+               {:is_reversion         false
+                :is_creation          true
+                :message              nil
+                :user                 (-> (user-details (mt/fetch-user :rasta))
+                                          (dissoc :email :date_joined :last_login :is_superuser :is_qbnewb))
+                :diff                 nil
+                :has_multiple_changes false
+                :description          "created this."}]
+              (doall (for [revision (mt/user-http-request :crowberto :get 200 (format "dashboard/%d/revisions" dashboard-id))]
+                       (dissoc revision :timestamp :id))))))))
 
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
diff --git a/test/metabase/api/revision_test.clj b/test/metabase/api/revision_test.clj
index bb9fbaa60779d84f12e34abd9c765617177dc03f..4a185798794f274fb5b4645aa1f81c9186296646 100644
--- a/test/metabase/api/revision_test.clj
+++ b/test/metabase/api/revision_test.clj
@@ -61,15 +61,15 @@
   (testing "Loading a single revision works"
     (t2.with-temp/with-temp [Card {:keys [id] :as card}]
       (create-card-revision! (:id card) true :rasta)
-      (is (= [{:is_reversion         false
-               :is_creation          true
-               :message              nil
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 nil
-               :has_multiple_changes false
-               :description          "created this."}]
-             (get-revisions :card id))))))
+      (is (=? [{:is_reversion         false
+                :is_creation          true
+                :message              nil
+                :user                 @rasta-revision-info
+                :metabase_version     config/mb-version-string
+                :diff                 nil
+                :has_multiple_changes false
+                :description          "created this."}]
+              (get-revisions :card id))))))
 
 (deftest get-revision-for-entity-with-revision-exceeds-max-revision-test
   (t2.with-temp/with-temp [Card {:keys [id] :as card} {:name "A card"}]
@@ -110,33 +110,33 @@
                   :message      "because i wanted to"
                   :is_creation  false
                   :is_reversion true)
-      (is (= [{:is_reversion         true
-               :is_creation          false
-               :message              "because i wanted to"
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 {:before {:name "something else"}
-                                      :after  {:name name}}
-               :description          "reverted to an earlier version."
-               :has_multiple_changes false}
-              {:is_reversion         false
-               :is_creation          false
-               :message              nil
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 {:before {:name name}
-                                      :after  {:name "something else"}}
-               :description          (format "renamed this Card from \"%s\" to \"something else\"." name)
-               :has_multiple_changes false}
-              {:is_reversion         false
-               :is_creation          true
-               :message              nil
-               :metabase_version     config/mb-version-string
-               :user                 @rasta-revision-info
-               :diff                 nil
-               :description          "created this."
-               :has_multiple_changes false}]
-             (get-revisions :card id))))))
+      (is (=? [{:is_reversion         true
+                :is_creation          false
+                :message              "because i wanted to"
+                :user                 @rasta-revision-info
+                :metabase_version     config/mb-version-string
+                :diff                 {:before {:name "something else"}
+                                       :after  {:name name}}
+                :description          "reverted to an earlier version."
+                :has_multiple_changes false}
+               {:is_reversion         false
+                :is_creation          false
+                :message              nil
+                :user                 @rasta-revision-info
+                :metabase_version     config/mb-version-string
+                :diff                 {:before {:name name}
+                                       :after  {:name "something else"}}
+                :description          (format "renamed this Card from \"%s\" to \"something else\"." name)
+                :has_multiple_changes false}
+               {:is_reversion         false
+                :is_creation          true
+                :message              nil
+                :metabase_version     config/mb-version-string
+                :user                 @rasta-revision-info
+                :diff                 nil
+                :description          "created this."
+                :has_multiple_changes false}]
+              (get-revisions :card id))))))
 
 ;;; # POST /revision/revert
 
@@ -181,48 +181,47 @@
                   (mt/user-http-request :rasta :post 200 "revision/revert" {:entity      :dashboard
                                                                             :id          id
                                                                             :revision_id previous-revision-id})))))
-      (is (= [{:is_reversion         true
-               :is_creation          false
-               :message              nil
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 {:before {:cards nil}
-                                      :after  {:cards [(merge default-revision-card {:card_id card-id :dashboard_id id})]}}
-               :has_multiple_changes false
-               :description          "reverted to an earlier version."}
-              {:is_reversion         false
-               :is_creation          false
-               :message              nil
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 {:before {:cards [(merge default-revision-card {:card_id card-id :dashboard_id id})]}
-                                      :after  {:cards nil}}
-               :has_multiple_changes false
-               :description          "removed a card."}
-              {:is_reversion         false
-               :is_creation          false
-               :message              nil
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 {:before {:cards nil}
-                                      :after  {:cards [(merge default-revision-card {:card_id card-id :dashboard_id id})]}}
-               :has_multiple_changes false
-               :description          "added a card."}
-              {:is_reversion         false
-               :is_creation          true
-               :message              nil
-               :user                 @rasta-revision-info
-               :metabase_version     config/mb-version-string
-               :diff                 nil
-               :has_multiple_changes false
-               :description          "created this."}]
-             (->> (get-revisions :dashboard id)
-                  (mapv (fn [rev]
-                          (if-not (:diff rev)
-                            rev
-                            (if (get-in rev [:diff :before :cards])
-                              (update-in rev [:diff :before :cards] strip-ids)
-                              (update-in rev [:diff :after :cards] strip-ids)))))))))))
+      (is (=? [{:is_reversion         true
+                :is_creation          false
+                :message              nil
+                :user                 @rasta-revision-info
+                :metabase_version     config/mb-version-string
+                :diff                 {:before {:cards nil}
+                                       :after  {:cards [(merge default-revision-card {:card_id card-id :dashboard_id id})]}}
+                :has_multiple_changes false
+                :description          "reverted to an earlier version."}
+               {:is_reversion         false
+                :is_creation          false
+                :message              nil
+                :user                 @rasta-revision-info
+                :metabase_version     config/mb-version-string
+                :diff                 {:before {:cards [(merge default-revision-card {:card_id card-id :dashboard_id id})]}
+                                       :after  {:cards nil}}
+                :has_multiple_changes false
+                :description          "removed a card."}
+               {:is_reversion         false
+                :is_creation          false
+                :message              nil
+                :user                 @rasta-revision-info
+                :diff                 {:before {:cards nil}
+                                       :after  {:cards [(merge default-revision-card {:card_id card-id :dashboard_id id})]}}
+                :has_multiple_changes false
+                :description          "added a card."}
+               {:is_reversion         false
+                :is_creation          true
+                :message              nil
+                :user                 @rasta-revision-info
+                :metabase_version     config/mb-version-string
+                :diff                 nil
+                :has_multiple_changes false
+                :description          "created this."}]
+              (->> (get-revisions :dashboard id)
+                   (mapv (fn [rev]
+                           (if-not (:diff rev)
+                             rev
+                             (if (get-in rev [:diff :before :cards])
+                               (update-in rev [:diff :before :cards] strip-ids)
+                               (update-in rev [:diff :after :cards] strip-ids)))))))))))
 
 (deftest permission-check-on-revert-test
   (testing "Are permissions enforced by the revert action in the revision api?"
diff --git a/test/metabase/api/search_test.clj b/test/metabase/api/search_test.clj
index de58acdb91129523d74a90a12d63fc38238ddacb..cdb105cbbea3854f5d51162b53a16b50217ad92d 100644
--- a/test/metabase/api/search_test.clj
+++ b/test/metabase/api/search_test.clj
@@ -3,19 +3,22 @@
    [clojure.set :as set]
    [clojure.string :as str]
    [clojure.test :refer :all]
+   [java-time :as t]
    [metabase.analytics.snowplow-test :as snowplow-test]
-   [metabase.api.common :as api]
    [metabase.api.search :as api.search]
    [metabase.mbql.normalize :as mbql.normalize]
    [metabase.models
     :refer [Action Card CardBookmark Collection Dashboard DashboardBookmark
             DashboardCard Database Metric PermissionsGroup
             PermissionsGroupMembership Pulse PulseCard QueryAction Segment Table]]
+   [metabase.models.collection :as collection]
    [metabase.models.model-index :as model-index]
    [metabase.models.permissions :as perms]
    [metabase.models.permissions-group :as perms-group]
+   [metabase.models.revision :as revision]
    [metabase.public-settings.premium-features :as premium-features]
-   [metabase.search.config :as search-config]
+   [metabase.public-settings.premium-features-test :as premium-features-test]
+   [metabase.search.config :as search.config]
    [metabase.search.scoring :as scoring]
    [metabase.test :as mt]
    [metabase.util :as u]
@@ -41,14 +44,21 @@
    :collection_authority_level nil
    :collection_position        nil
    :context                    nil
+   :created_at                 true
+   :creator_common_name        nil
+   :creator_id                 false
    :dashboardcard_count        nil
    :database_id                false
+   :dataset_query              nil
    :description                nil
    :id                         true
    :initial_sync_status        nil
    :model_id                   false
    :model_name                 nil
    :moderated_status           nil
+   :last_editor_common_name    nil
+   :last_editor_id             false
+   :last_edited_at             false
    :pk_ref                     nil
    :model_index_id             false ;; columns ending in _id get booleaned
    :table_description          nil
@@ -64,7 +74,7 @@
   (merge
    {:table_id true, :database_id true}
    (t2/select-one [Table [:name :table_name] [:schema :table_schema] [:description :table_description]]
-     :id (mt/id :checkins))))
+                  :id (mt/id :checkins))))
 
 (defn- sorted-results [results]
   (->> results
@@ -91,16 +101,17 @@
 
 (defn- default-search-results []
   (sorted-results
-   [(make-result "dashboard test dashboard", :model "dashboard", :bookmark false)
+   [(make-result "dashboard test dashboard", :model "dashboard", :bookmark false :creator_id true :creator_common_name "Rasta Toucan")
     test-collection
-    (make-result "card test card", :model "card", :bookmark false, :dashboardcard_count 0)
-    (make-result "dataset test dataset", :model "dataset", :bookmark false, :dashboardcard_count 0)
-    (make-result "action test action", :model "action", :model_name (:name action-model-params), :model_id true, :database_id true)
+    (make-result "card test card", :model "card", :bookmark false, :dashboardcard_count 0 :creator_id true :creator_common_name "Rasta Toucan" :dataset_query nil)
+    (make-result "dataset test dataset", :model "dataset", :bookmark false, :dashboardcard_count 0 :creator_id true :creator_common_name "Rasta Toucan" :dataset_query nil)
+    (make-result "action test action", :model "action", :model_name (:name action-model-params), :model_id true,
+                 :database_id true :creator_id true :creator_common_name "Rasta Toucan" :dataset_query (update (mt/query venues) :type name))
     (merge
-     (make-result "metric test metric", :model "metric", :description "Lookin' for a blueberry")
+     (make-result "metric test metric", :model "metric", :description "Lookin' for a blueberry" :creator_id true :creator_common_name "Rasta Toucan")
      (table-search-results))
     (merge
-     (make-result "segment test segment", :model "segment", :description "Lookin' for a blueberry")
+     (make-result "segment test segment", :model "segment", :description "Lookin' for a blueberry" :creator_id true :creator_common_name "Rasta Toucan")
      (table-search-results))]))
 
 (defn- default-metric-segment-results []
@@ -136,6 +147,11 @@
                    Action      {action-id :id
                                 :as action}   (merge (data-map "action %s action")
                                                      {:type :query, :model_id (u/the-id action-model)})
+                   Database    {db-id :id
+                                :as db}       (data-map "database %s database")
+                   Table       table          (merge (data-map "database %s database")
+                                                     {:db_id db-id})
+
                    QueryAction _qa (query-action action-id)
                    Card        card           (coll-data-map "card %s card" coll)
                    Card        dataset        (assoc (coll-data-map "dataset %s dataset" coll)
@@ -146,9 +162,11 @@
       (f {:action     action
           :collection coll
           :card       card
+          :database   db
           :dataset    dataset
           :dashboard  dashboard
           :metric     metric
+          :table      table
           :segment    segment}))))
 
 (defmacro ^:private with-search-items-in-root-collection [search-string & body]
@@ -225,6 +243,7 @@
              [:like [:lower :table_name]        "%foo%"] [:inline 0]
              [:like [:lower :table_description] "%foo%"] [:inline 0]
              [:like [:lower :model_name]        "%foo%"] [:inline 0]
+             [:like [:lower :dataset_query]     "%foo%"] [:inline 0]
              :else [:inline 1]]]
            (api.search/order-clause "Foo")))))
 
@@ -240,7 +259,7 @@
                (search-request-data :crowberto :q "test"))))))
   (testing "It prioritizes exact matches"
     (with-search-items-in-root-collection "test"
-      (with-redefs [search-config/*db-max-results* 1]
+      (with-redefs [search.config/*db-max-results* 1]
         (is (= [test-collection]
                (search-request-data :crowberto :q "test collection"))))))
   (testing "It limits matches properly"
@@ -272,7 +291,7 @@
       (is (= 2 (:limit (search-request :crowberto :q "test" :limit "2" :offset "3"))))
       (is (= 3 (:offset (search-request :crowberto :q "test" :limit "2" :offset "3")))))))
 
-(deftest query-model-set
+(deftest archived-models-test
   (testing "It returns some stuff when you get results"
     (with-search-items-in-root-collection "test"
       ;; sometimes there is a "table" in these responses. might be do to garbage in CI
@@ -287,17 +306,49 @@
 (deftest query-model-set-test
   (let [search-term "query-model-set"]
     (with-search-items-in-root-collection search-term
-      (testing "should return a list of models that search result will return"
-        (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card"}
+      (testing "should returns a list of models that search result will return"
+        (is (= #{"dashboard" "table" "dataset" "segment" "collection" "database" "action" "metric" "card"}
                (set (mt/user-http-request :crowberto :get 200 "search/models" :q search-term)))))
-      (testing "should not return models when there is no search result"
-        (is (= #{}
-               (set (mt/user-http-request :crowberto :get 200 "search/models" :q "noresults"))))))))
+      (testing "return a subset of model for created-by filter"
+        (is (= #{"dashboard" "dataset" "card" "action"}
+               (set (mt/user-http-request :crowberto :get 200 "search/models"
+                                          :q search-term
+                                          :created_by (mt/user->id :rasta))))))
+      (testing "return a subset of model for verified filter"
+        (t2.with-temp/with-temp
+          [:model/Card       {v-card-id :id}  {:name (format "%s Verified Card" search-term)}
+           :model/Card       {v-model-id :id} {:name (format "%s Verified Model" search-term) :dataset true}
+           :model/Collection {_v-coll-id :id} {:name (format "%s Verified Collection" search-term) :authority_level "official"}]
+          (testing "when has both :content-verification features"
+            (premium-features-test/with-premium-features #{:content-verification}
+              (mt/with-verified-cards [v-card-id v-model-id]
+                (is (= #{"card" "dataset"}
+                       (set (mt/user-http-request :crowberto :get 200 "search/models"
+                                                  :q search-term
+                                                  :verified true)))))))
+          (testing "when has :content-verification feature only"
+            (premium-features-test/with-premium-features #{:content-verification}
+              (mt/with-verified-cards [v-card-id]
+                (is (= #{"card"}
+                       (set (mt/user-http-request :crowberto :get 200 "search/models"
+                                                  :q search-term
+                                                  :verified true)))))))))
+      (testing "return a subset of model for created_at filter"
+        (is (= #{"dashboard" "table" "dataset" "collection" "database" "action" "card"}
+               (set (mt/user-http-request :crowberto :get 200 "search/models"
+                                          :q search-term
+                                          :created_at "today")))))
+
+      (testing "return a subset of model for search_native_query filter"
+        (is (= #{"dataset" "action" "card"}
+               (set (mt/user-http-request :crowberto :get 200 "search/models"
+                                          :q search-term
+                                          :search_native_query true))))))))
 
 (def ^:private dashboard-count-results
   (letfn [(make-card [dashboard-count]
             (make-result (str "dashboard-count " dashboard-count) :dashboardcard_count dashboard-count,
-                         :model "card", :bookmark false))]
+                         :model "card", :bookmark false :creator_id true :creator_common_name "Rasta Toucan" :dataset_query nil))]
     (set [(make-card 5)
           (make-card 3)
           (make-card 0)])))
@@ -345,11 +396,13 @@
             (perms/grant-collection-read-permissions! group (u/the-id collection))
             (is (= (sorted-results
                     (reverse ;; This reverse is hokey; it's because the test2 results happen to come first in the API response
-                     (into
-                      (default-results-with-collection)
-                      (map #(merge default-search-row % (table-search-results))
-                           [{:name "metric test2 metric", :description "Lookin' for a blueberry", :model "metric"}
-                            {:name "segment test2 segment", :description "Lookin' for a blueberry", :model "segment"}]))))
+                             (into
+                              (default-results-with-collection)
+                              (map #(merge default-search-row % (table-search-results))
+                                   [{:name "metric test2 metric", :description "Lookin' for a blueberry",
+                                     :model "metric" :creator_id true :creator_common_name "Rasta Toucan"}
+                                    {:name "segment test2 segment", :description "Lookin' for a blueberry",
+                                     :model "segment" :creator_id true :creator_common_name "Rasta Toucan"}]))))
                    (search-request-data :rasta :q "test"))))))))
 
   (testing (str "Users with root collection permissions should be able to search root collection data long with "
@@ -370,69 +423,71 @@
                                       (update row :name #(str/replace % "test" "test2"))))))
                                  (search-request-data :rasta :q "test"))))))))
 
-  (testing "Users with access to multiple collections should see results from all collections they have access to"
-    (with-search-items-in-collection {coll-1 :collection} "test"
-      (with-search-items-in-collection {coll-2 :collection} "test2"
-        (mt/with-temp [PermissionsGroup           group {}
-                       PermissionsGroupMembership _ {:user_id (mt/user->id :rasta) :group_id (u/the-id group)}]
-          (perms/grant-collection-read-permissions! group (u/the-id coll-1))
-          (perms/grant-collection-read-permissions! group (u/the-id coll-2))
-          (is (ordered-subset? (sorted-results
-                                (reverse
-                                 (into
-                                  (default-results-with-collection)
-                                  (map (fn [row] (update row :name #(str/replace % "test" "test2")))
-                                       (default-results-with-collection)))))
-                               (search-request-data :rasta :q "test")))))))
-
-  (testing "User should only see results in the collection they have access to"
-    (mt/with-non-admin-groups-no-root-collection-perms
-      (with-search-items-in-collection {coll-1 :collection} "test"
-        (with-search-items-in-collection _ "test2"
-          (mt/with-temp [PermissionsGroup           group {}
-                         PermissionsGroupMembership _ {:user_id (mt/user->id :rasta) :group_id (u/the-id group)}]
-            (perms/grant-collection-read-permissions! group (u/the-id coll-1))
-            (is (= (sorted-results
-                    (reverse
-                     (into
-                      (default-results-with-collection)
-                      (map #(merge default-search-row % (table-search-results))
-                           [{:name "metric test2 metric" :description "Lookin' for a blueberry" :model "metric"}
-                            {:name "segment test2 segment" :description "Lookin' for a blueberry" :model "segment"}]))))
-                   (search-request-data :rasta :q "test"))))))))
-
-  (testing "Metrics on tables for which the user does not have access to should not show up in results"
-    (mt/with-temp [Database {db-id :id} {}
-                   Table    {table-id :id} {:db_id  db-id
-                                            :schema nil}
-                   Metric   _ {:table_id table-id
-                               :name     "test metric"}]
-      (perms/revoke-data-perms! (perms-group/all-users) db-id)
-      (is (= []
-             (search-request-data :rasta :q "test")))))
-
-  (testing "Segments on tables for which the user does not have access to should not show up in results"
-    (mt/with-temp [Database {db-id :id} {}
-                   Table    {table-id :id} {:db_id  db-id
-                                            :schema nil}
-                   Segment  _ {:table_id table-id
-                               :name     "test segment"}]
-      (perms/revoke-data-perms! (perms-group/all-users) db-id)
-      (is (= []
-             (search-request-data :rasta :q "test")))))
-
-  (testing "Databases for which the user does not have access to should not show up in results"
-    (mt/with-temp [Database db-1  {:name "db-1"}
-                   Database _db-2 {:name "db-2"}]
-      (is (set/subset? #{"db-2" "db-1"}
-                       (->> (search-request-data-with sorted-results :rasta :q "db")
-                            (map :name)
-                            set)))
-      (perms/revoke-data-perms! (perms-group/all-users) (:id db-1))
-      (is (nil? ((->> (search-request-data-with sorted-results :rasta :q "db")
-                      (map :name)
-                      set)
-                 "db-1"))))))
+ (testing "Users with access to multiple collections should see results from all collections they have access to"
+   (with-search-items-in-collection {coll-1 :collection} "test"
+     (with-search-items-in-collection {coll-2 :collection} "test2"
+       (mt/with-temp [PermissionsGroup           group {}
+                      PermissionsGroupMembership _ {:user_id (mt/user->id :rasta) :group_id (u/the-id group)}]
+         (perms/grant-collection-read-permissions! group (u/the-id coll-1))
+         (perms/grant-collection-read-permissions! group (u/the-id coll-2))
+         (is (ordered-subset? (sorted-results
+                               (reverse
+                                (into
+                                 (default-results-with-collection)
+                                 (map (fn [row] (update row :name #(str/replace % "test" "test2")))
+                                      (default-results-with-collection)))))
+                              (search-request-data :rasta :q "test")))))))
+
+ (testing "User should only see results in the collection they have access to"
+   (mt/with-non-admin-groups-no-root-collection-perms
+     (with-search-items-in-collection {coll-1 :collection} "test"
+       (with-search-items-in-collection _ "test2"
+         (mt/with-temp [PermissionsGroup           group {}
+                        PermissionsGroupMembership _ {:user_id (mt/user->id :rasta) :group_id (u/the-id group)}]
+           (perms/grant-collection-read-permissions! group (u/the-id coll-1))
+           (is (= (sorted-results
+                   (reverse
+                    (into
+                     (default-results-with-collection)
+                     (map #(merge default-search-row % (table-search-results))
+                          [{:name "metric test2 metric" :description "Lookin' for a blueberry"
+                            :model "metric" :creator_id true :creator_common_name "Rasta Toucan"}
+                           {:name "segment test2 segment" :description "Lookin' for a blueberry" :model "segment"
+                            :creator_id true :creator_common_name "Rasta Toucan"}]))))
+                  (search-request-data :rasta :q "test"))))))))
+
+ (testing "Metrics on tables for which the user does not have access to should not show up in results"
+   (mt/with-temp [Database {db-id :id} {}
+                  Table    {table-id :id} {:db_id  db-id
+                                           :schema nil}
+                  Metric   _ {:table_id table-id
+                              :name     "test metric"}]
+     (perms/revoke-data-perms! (perms-group/all-users) db-id)
+     (is (= []
+            (search-request-data :rasta :q "test")))))
+
+ (testing "Segments on tables for which the user does not have access to should not show up in results"
+   (mt/with-temp [Database {db-id :id} {}
+                  Table    {table-id :id} {:db_id  db-id
+                                           :schema nil}
+                  Segment  _ {:table_id table-id
+                              :name     "test segment"}]
+     (perms/revoke-data-perms! (perms-group/all-users) db-id)
+     (is (= []
+            (search-request-data :rasta :q "test")))))
+
+ (testing "Databases for which the user does not have access to should not show up in results"
+   (mt/with-temp [Database db-1  {:name "db-1"}
+                  Database _db-2 {:name "db-2"}]
+     (is (set/subset? #{"db-2" "db-1"}
+                      (->> (search-request-data-with sorted-results :rasta :q "db")
+                           (map :name)
+                           set)))
+     (perms/revoke-data-perms! (perms-group/all-users) (:id db-1))
+     (is (nil? ((->> (search-request-data-with sorted-results :rasta :q "db")
+                     (map :name)
+                     set)
+                "db-1"))))))
 
 (deftest bookmarks-test
   (testing "Bookmarks are per user, so other user's bookmarks don't cause search results to be altered"
@@ -513,21 +568,7 @@
                                  :model_name     (:name model)
                                  :model_index_id #hawk/malli :int}}
                       (into {} (comp relevant (map (juxt :name normalize)))
-                            (search! "rom"))))))))))
-
-  (testing "Sandboxing inhibits searching indexes"
-    (binding [api/*current-user-id* (mt/user->id :rasta)]
-      (is (= [:and
-              [:inline [:= 1 1]]
-              [:or [:like [:lower :model-index-value.name] "%foo%"]]]
-             (#'api.search/base-where-clause-for-model "indexed-entity" {:archived? false
-                                                                         :search-string "foo"
-                                                                         :current-user-perms #{"/"}})))
-      (with-redefs [premium-features/sandboxed-or-impersonated-user? (constantly true)]
-        (is (= [:and [:inline [:= 1 1]] [:or [:= 0 1]]]
-               (#'api.search/base-where-clause-for-model "indexed-entity" {:archived? false
-                                                                           :search-string "foo"
-                                                                           :current-user-perms #{"/"}})))))))
+                            (search! "rom")))))))))))
 
 (deftest archived-results-test
   (testing "Should return unarchived results by default"
@@ -615,7 +656,7 @@
 
 (deftest table-test
   (testing "You should see Tables in the search results!\n"
-    (t2.with-temp/with-temp [Table _ {:name "RoundTable"}]
+    (mt/with-temp [Table _ {:name "RoundTable"}]
       (do-test-users [user [:crowberto :rasta]]
         (is (= [(default-table-search-row "RoundTable")]
                (search-request-data user :q "RoundTable"))))))
@@ -627,25 +668,25 @@
                (search-request-data user :q "Foo"))))))
   (testing "You should be able to search by their display name"
     (let [lancelot "Lancelot's Favorite Furniture"]
-      (t2.with-temp/with-temp [Table _ {:name "RoundTable" :display_name lancelot}]
+      (mt/with-temp [Table _ {:name "RoundTable" :display_name lancelot}]
         (do-test-users [user [:crowberto :rasta]]
           (is (= [(assoc (default-table-search-row "RoundTable") :name lancelot)]
                  (search-request-data user :q "Lancelot")))))))
   (testing "You should be able to search by their description"
     (let [lancelot "Lancelot's Favorite Furniture"]
-      (t2.with-temp/with-temp [Table _ {:name "RoundTable" :description lancelot}]
+      (mt/with-temp [Table _ {:name "RoundTable" :description lancelot}]
         (do-test-users [user [:crowberto :rasta]]
           (is (= [(assoc (default-table-search-row "RoundTable") :description lancelot :table_description lancelot)]
                  (search-request-data user :q "Lancelot")))))))
   (testing "When searching with ?archived=true, normal Tables should not show up in the results"
     (let [table-name (mt/random-name)]
-      (t2.with-temp/with-temp [Table _ {:name table-name}]
+      (mt/with-temp [Table _ {:name table-name}]
         (do-test-users [user [:crowberto :rasta]]
           (is (= []
                  (search-request-data user :q table-name :archived true)))))))
   (testing "*archived* tables should not appear in search results"
     (let [table-name (mt/random-name)]
-      (t2.with-temp/with-temp [Table _ {:name table-name, :active false}]
+      (mt/with-temp [Table _ {:name table-name, :active false}]
         (do-test-users [user [:crowberto :rasta]]
           (is (= []
                  (search-request-data user :q table-name)))))))
@@ -737,14 +778,17 @@
                                :name     "segment count test 2"}
      Segment   _              {:table_id table-id
                                :name     "segment count test 3"}]
-    (with-redefs [premium-features/sandboxed-or-impersonated-user? (constantly false)]
+    (mt/with-current-user (mt/user->id :crowberto)
       (t2.execute/with-call-count [call-count]
-        ;; there seems to be a bug with mu/def that if this is a list it fails validation
-        (#'api.search/search (#'api.search/search-context "count test" nil nil nil 100 0))
+        (#'api.search/search {:search-string      "count test"
+                              :archived?          false
+                              :models             search.config/all-models
+                              :current-user-perms #{"/"}
+                              :limit-int          100})
         ;; the call count number here are expected to change if we change the search api
         ;; we have this test here just to keep tracks this number to remind us to put effort
         ;; into keep this number as low as we can
-        (is (= 16 (call-count)))))))
+        (is (= 11 (call-count)))))))
 
 (deftest snowplow-new-search-query-event-test
   (testing "Send a snowplow event when a new global search query is made"
@@ -765,27 +809,550 @@
       (mt/user-http-request :crowberto :get 200 "search" :q "test" :archived true)
       (is (empty? (snowplow-test/pop-event-data-and-user-id!))))))
 
-(deftest available-models-should-be-independent-of-models-param-test
-  (testing "if a search request includes `models` params, the `available_models` from the response should not be restricted by it"
-    (let [search-term "Available models"]
-      (with-search-items-in-root-collection search-term
-        (testing "GET /api/search"
-          (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card"}
-                 (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :models "card")
-                     :available_models
-                     set)))
+;; ------------------------------------------------ Filter Tests ------------------------------------------------ ;;
 
-          (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card"}
-                 (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :models "card" :models "dashboard")
+(deftest filter-by-creator-test
+  (let [search-term "Created by Filter"]
+    (with-search-items-in-root-collection search-term
+      (mt/with-temp
+        [:model/User      {user-id :id}      {:first_name "Explorer" :last_name "Curious"}
+         :model/User      {user-id-2 :id}    {:first_name "Explorer" :last_name "Hubble"}
+         :model/Card      {card-id :id}      {:name (format "%s Card 1" search-term) :creator_id user-id}
+         :model/Card      {card-id-2 :id}    {:name (format "%s Card 2" search-term) :creator_id user-id
+                                              :collection_id (:id (collection/user->personal-collection user-id))}
+         :model/Card      {card-id-3 :id}    {:name (format "%s Card 3" search-term) :creator_id user-id :archived true}
+         :model/Card      {card-id-4 :id}    {:name (format "%s Card 4" search-term) :creator_id user-id-2}
+         :model/Card      {model-id :id}     {:name (format "%s Dataset 1" search-term) :dataset true :creator_id user-id}
+         :model/Dashboard {dashboard-id :id} {:name (format "%s Dashboard 1" search-term) :creator_id user-id}
+         :model/Action    {action-id :id}    {:name (format "%s Action 1" search-term) :model_id model-id :creator_id user-id :type :http}]
+
+        (testing "sanity check that without search by created_by we have more results than if a filter is provided"
+          (is (> (:total (mt/user-http-request :crowberto :get 200 "search" :q search-term))
+                 5)))
+
+        (testing "Able to filter by creator"
+          (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_by user-id)]
+
+            (testing "only a subset of models are applicable"
+              (is (= #{"card" "dataset" "dashboard" "action"} (set (:available_models resp)))))
+
+            (testing "results contains only entities with the specified creator"
+              (is (= #{[dashboard-id "dashboard" "Created by Filter Dashboard 1"]
+                       [card-id      "card"      "Created by Filter Card 1"]
+                       [card-id-2    "card"      "Created by Filter Card 2"]
+                       [model-id     "dataset"   "Created by Filter Dataset 1"]
+                       [action-id    "action"    "Created by Filter Action 1"]}
+                     (->> (:data resp)
+                          (map (juxt :id :model :name))
+                          set))))))
+
+        (testing "Able to filter by multiple creators"
+          (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_by user-id :created_by user-id-2)]
+
+            (testing "only a subset of models are applicable"
+              (is (= #{"card" "dataset" "dashboard" "action"} (set (:available_models resp)))))
+
+            (testing "results contains only entities with the specified creator"
+              (is (= #{[dashboard-id "dashboard" "Created by Filter Dashboard 1"]
+                       [card-id      "card"      "Created by Filter Card 1"]
+                       [card-id-2    "card"      "Created by Filter Card 2"]
+                       [card-id-4    "card"      "Created by Filter Card 4"]
+                       [model-id     "dataset"   "Created by Filter Dataset 1"]
+                       [action-id    "action"    "Created by Filter Action 1"]}
+                     (->> (:data resp)
+                          (map (juxt :id :model :name))
+                          set))))))
+
+       (testing "Works with archived filter"
+         (is (=? [{:model "card"
+                   :id     card-id-3
+                   :archived true}]
+                 (:data (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_by user-id :archived true)))))
+
+       (testing "Works with models filter"
+         (testing "return intersections of supported models with provided models"
+           (is (= #{"dashboard" "card"}
+                  (->> (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_by user-id :models "card" :models "dashboard")
+                       :data
+                       (map :model)
+                       set))))
+
+         (testing "return nothing if there is no intersection"
+           (is (= #{}
+                  (->> (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_by user-id :models "table" :models "database")
+                       :data
+                       (map :model)
+                       set)))))
+
+       (testing "respect the read permissions"
+         (let [resp (mt/user-http-request :rasta :get 200 "search" :q search-term :created_by user-id)]
+           (is (not (contains?
+                     (->> (:data resp)
+                          (filter #(= (:model %) "card"))
+                          (map :id)
+                          set)
+                     card-id-2)))))
+
+       (testing "error if creator_id is not an integer"
+         (let [resp (mt/user-http-request :crowberto :get 400 "search" :q search-term :created_by "not-a-valid-user-id")]
+           (is (= {:created_by "nullable value must be an integer greater than zero., or sequence of value must be an integer greater than zero."}
+                  (:errors resp)))))))))
+
+(deftest filter-by-last-edited-by-test
+  (let [search-term "last-edited-by"]
+    (mt/with-temp
+      [:model/Card       {rasta-card-id :id}   {:name search-term}
+       :model/Card       {lucky-card-id :id}   {:name search-term}
+       :model/Card       {rasta-model-id :id}  {:name search-term :dataset true}
+       :model/Card       {lucky-model-id :id}  {:name search-term :dataset true}
+       :model/Dashboard  {rasta-dash-id :id}   {:name search-term}
+       :model/Dashboard  {lucky-dash-id :id}   {:name search-term}
+       :model/Metric     {rasta-metric-id :id} {:name search-term}
+       :model/Metric     {lucky-metric-id :id} {:name search-term}]
+      (let [rasta-user-id (mt/user->id :rasta)
+            lucky-user-id (mt/user->id :lucky)]
+        (doseq [[model id user-id] [[:model/Card rasta-card-id rasta-user-id] [:model/Card rasta-model-id rasta-user-id]
+                                    [:model/Dashboard rasta-dash-id rasta-user-id] [:model/Metric rasta-metric-id rasta-user-id]
+                                    [:model/Card lucky-card-id lucky-user-id] [:model/Card lucky-model-id lucky-user-id]
+                                    [:model/Dashboard lucky-dash-id lucky-user-id] [:model/Metric lucky-metric-id lucky-user-id]]]
+          (revision/push-revision!
+           :entity      model
+           :id          id
+           :user-id     user-id
+           :is_creation true
+           :object      {:id id}))
+
+        (testing "Able to filter by last editor"
+          (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :last_edited_by rasta-user-id)]
+
+            (testing "only a subset of models are applicable"
+              (is (= #{"dashboard" "dataset" "metric" "card"} (set (:available_models resp)))))
+
+            (testing "results contains only entities with the specified creator"
+              (is (= #{[rasta-metric-id "metric"]
+                       [rasta-card-id   "card"]
+                       [rasta-model-id  "dataset"]
+                       [rasta-dash-id   "dashboard"]}
+                     (->> (:data resp)
+                          (map (juxt :id :model))
+                          set))))))
+
+        (testing "Able to filter by multiple last editor"
+          (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :last_edited_by rasta-user-id :last_edited_by lucky-user-id)]
+
+            (testing "only a subset of models are applicable"
+              (is (= #{"dashboard" "dataset" "metric" "card"} (set (:available_models resp)))))
+
+            (testing "results contains only entities with the specified creator"
+              (is (= #{[rasta-metric-id "metric"]
+                       [rasta-card-id   "card"]
+                       [rasta-model-id  "dataset"]
+                       [rasta-dash-id   "dashboard"]
+                       [lucky-metric-id "metric"]
+                       [lucky-card-id   "card"]
+                       [lucky-model-id  "dataset"]
+                       [lucky-dash-id   "dashboard"]}
+                     (->> (:data resp)
+                          (map (juxt :id :model))
+                          set))))))
+
+       (testing "error if last_edited_by is not an integer"
+         (let [resp (mt/user-http-request :crowberto :get 400 "search" :q search-term :last_edited_by "not-a-valid-user-id")]
+           (is (= {:last_edited_by "nullable value must be an integer greater than zero., or sequence of value must be an integer greater than zero."}
+                  (:errors resp)))))))))
+
+(deftest verified-filter-test
+  (let [search-term "Verified filter"]
+    (t2.with-temp/with-temp
+      [:model/Card {v-card-id :id}  {:name (format "%s Verified Card" search-term)}
+       :model/Card {_card-id :id}   {:name (format "%s Normal Card" search-term)}
+       :model/Card {_model-id :id}  {:name (format "%s Normal Model" search-term) :dataset true}
+       :model/Card {v-model-id :id} {:name (format "%s Verified Model" search-term) :dataset true}]
+      (mt/with-verified-cards [v-card-id v-model-id]
+        (premium-features-test/with-premium-features #{:content-verification}
+          (testing "Able to filter only verified items"
+            (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :verified true)]
+              (testing "do not returns duplicated verified cards"
+                (is (= 1 (->> resp
+                              :data
+                              (filter #(= {:model "card" :id v-card-id} (select-keys % [:model :id])))
+                              count))))
+
+              (testing "only a subset of models are applicable"
+                (is (= #{"card" "dataset"} (set (:available_models resp)))))
+
+              (testing "results contains only verified entities"
+                (is (= #{[v-card-id  "card"       "Verified filter Verified Card"]
+                         [v-model-id "dataset"    "Verified filter Verified Model"]}
+
+                       (->> (:data resp)
+                            (map (juxt :id :model :name))
+                            set))))))
+
+          (testing "Returns schema error if attempt to serach for non-verified items"
+            (is (= {:verified "nullable true"}
+                   (:errors (mt/user-http-request :crowberto :get 400 "search" :q "x" :verified false)))))
+
+          (testing "Works with models filter"
+            (testing "return intersections of supported models with provided models"
+              (is (= #{"card"}
+                     (->> (mt/user-http-request :crowberto :get 200 "search"
+                                                :q search-term :verified true :models "card" :models "dashboard" :model "table")
+                          :data
+                          (map :model)
+                          set))))))
+
+        (premium-features-test/with-premium-features #{:content-verification}
+          (testing "Returns verified cards and models only if :content-verification is enabled"
+            (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :verified true)]
+
+              (testing "only a subset of models are applicable"
+                (is (= #{"card" "dataset"} (set (:available_models resp)))))
+
+              (testing "results contains only verified entities"
+                (is (= #{[v-card-id  "card"    "Verified filter Verified Card"]
+                         [v-model-id "dataset" "Verified filter Verified Model"]}
+                       (->> (:data resp)
+                            (map (juxt :id :model :name))
+                            set)))))))
+
+       (testing "error if doesn't have premium-features"
+         (premium-features-test/with-premium-features #{}
+           (is (= "Content Management or Official Collections is a paid feature not currently available to your instance. Please upgrade to use it. Learn more at metabase.com/upgrade/"
+                  (mt/user-http-request :crowberto :get 402 "search" :q search-term :verified true)))))))))
+
+(deftest created-at-api-test
+  (let [search-term "created-at-filtering"]
+    (with-search-items-in-root-collection search-term
+      (testing "returns only applicable models"
+        (is (= #{"dashboard" "table" "dataset" "collection" "database" "action" "card"}
+               (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_at "today")
+                   :available_models
+                   set))))
+
+      (testing "works with others filter too"
+        (is (= #{"dashboard" "table" "dataset" "collection" "database" "action" "card"}
+               (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :created_at "today" :creator_id (mt/user->id :rasta))
+                   :available_models
+                   set))))
+
+      (testing "error if invalids created_at string"
+        (is (= "Failed to parse datetime value: today~"
+               (mt/user-http-request :crowberto :get 400 "search" :q search-term :created_at "today~" :creator_id (mt/user->id :rasta))))))))
+
+(deftest filter-by-last-edited-at-test
+  (let [search-term "last-edited-at-filtering"]
+    (t2.with-temp/with-temp
+      [:model/Card       {card-id :id}   {:name search-term}
+       :model/Card       {model-id :id}  {:name search-term :dataset true}
+       :model/Dashboard  {dash-id :id}   {:name search-term}
+       :model/Metric     {metric-id :id} {:name search-term}
+       :model/Action     {action-id :id} {:name       search-term
+                                          :model_id   model-id
+                                          :type       :http}]
+      (doseq [[model id] [[:model/Card card-id] [:model/Card model-id]
+                          [:model/Dashboard dash-id] [:model/Metric metric-id]]]
+        (revision/push-revision!
+         :entity      model
+         :id          id
+         :user-id     (mt/user->id :rasta)
+         :is_creation true
+         :object      {:id id}))
+      (testing "returns only applicable models"
+        (let [resp (mt/user-http-request :crowberto :get 200 "search" :q search-term :last_edited_at "today")]
+          (is (= #{[action-id "action"]
+                   [card-id   "card"]
+                   [dash-id   "dashboard"]
+                   [model-id  "dataset"]
+                   [metric-id "metric"]}
+                 (->> (:data resp)
+                      (map (juxt :id :model))
+                      set)))
+
+          (is (= #{"action" "card" "dashboard" "dataset" "metric"}
+                 (-> resp
                      :available_models
-                     set))))
-
-        (testing "GET /api/search/models"
-          (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card"}
-                 (set (mt/user-http-request :crowberto :get 200 "search/models" :q search-term :models "card"))))
+                     set)))))
+
+      (testing "works with the last_edited_by filter too"
+        (doseq [[model id] [[:model/Card card-id] [:model/Card model-id]
+                            [:model/Dashboard dash-id] [:model/Metric metric-id]]]
+          (revision/push-revision!
+           :entity      model
+           :id          id
+           :user-id     (mt/user->id :rasta)
+           :is_creation true
+           :object      {:id id}))
+        (is (= #{"dashboard" "dataset" "metric" "card"}
+               (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :last_edited_at "today" :last_edited_by (mt/user->id :rasta))
+                   :available_models
+                   set))))
+
+      (testing "error if invalids last_edited_at string"
+        (is (= "Failed to parse datetime value: today~"
+               (mt/user-http-request :crowberto :get 400 "search" :q search-term :last_edited_at "today~" :creator_id (mt/user->id :rasta))))))))
+
+(deftest created-at-correctness-test
+  (let [search-term "created-at-filtering"
+        new          #t "2023-05-04T10:00Z[UTC]"
+        two-years-ago (t/minus new (t/years 2))]
+    (mt/with-clock new
+      (t2.with-temp/with-temp
+        [:model/Dashboard  {dashboard-new :id}{:name       search-term
+                                               :created_at new}
+         :model/Dashboard  {dashboard-old :id}{:name       search-term
+                                               :created_at two-years-ago}
+         :model/Database   {db-new :id}       {:name       search-term
+                                               :created_at new}
+         :model/Database   {db-old :id }      {:name       search-term
+                                               :created_at two-years-ago}
+         :model/Table      {table-new :id}    {:name       search-term
+                                               :db_id      db-new
+                                               :created_at new}
+         :model/Table      {table-old :id}    {:name       search-term
+                                               :db_id      db-old
+                                               :created_at two-years-ago}
+         :model/Collection {coll-new :id}     {:name       search-term
+                                               :created_at new}
+         :model/Collection {coll-old :id}     {:name       search-term
+                                               :created_at two-years-ago}
+         :model/Card       {card-new :id}     {:name       search-term
+                                               :created_at new}
+         :model/Card       {card-old :id}     {:name       search-term
+                                               :created_at two-years-ago}
+         :model/Card       {model-new :id}    {:name       search-term
+                                               :dataset    true
+                                               :created_at new}
+         :model/Card       {model-old :id}    {:name       search-term
+                                               :dataset    true
+                                               :created_at two-years-ago}
+         :model/Action     {action-new :id}   {:name       search-term
+                                               :model_id   model-new
+                                               :type       :http
+                                               :created_at new}
+         :model/Action     {action-old :id}   {:name       search-term
+                                               :model_id   model-old
+                                               :type       :http
+                                               :created_at two-years-ago}
+         :model/Segment    {_segment-new :id} {:name       search-term
+                                               :created_at new}
+         :model/Metric     {_metric-new :id}  {:name       search-term
+                                               :created_at new}]
+        ;; with clock doesn't work if calling via API, so we call the search function directly
+        (let [test-search (fn [created-at expected]
+                           (testing (format "searching with created-at = %s" created-at)
+                             (mt/with-current-user (mt/user->id :crowberto)
+                               (is (= expected
+                                      (->> (#'api.search/search (#'api.search/search-context
+                                                                 {:search-string search-term
+                                                                  :archived      false
+                                                                  :models        search.config/all-models
+                                                                  :created-at    created-at}))
+                                           :data
+                                           (map (juxt :model :id))
+                                           set))))))
+              new-result  #{["action"     action-new]
+                            ["card"       card-new]
+                            ["collection" coll-new]
+                            ["database"   db-new]
+                            ["dataset"    model-new]
+                            ["dashboard"  dashboard-new]
+                            ["table"      table-new]}
+              old-result  #{["action"     action-old]
+                            ["card"       card-old]
+                            ["collection" coll-old]
+                            ["database"   db-old]
+                            ["dataset"    model-old]
+                            ["dashboard"  dashboard-old]
+                            ["table"      table-old]}]
+          ;; absolute datetime
+         (test-search "Q2-2021" old-result)
+         (test-search "2023-05-04" new-result)
+         (test-search "2021-05-03~" (set/union old-result new-result))
+         ;; range is inclusive of the start but exclusive of the end, so this does not contain new-result
+         (test-search "2021-05-04~2023-05-03" old-result)
+         (test-search "2021-05-05~2023-05-04" new-result)
+         (test-search "~2023-05-03" old-result)
+         (test-search "2021-05-04T09:00:00~2021-05-04T10:00:10" old-result)
+
+         ;; relative times
+         (test-search "thisyear" new-result)
+         (test-search "past1years-from-12months" old-result)
+         (test-search "today" new-result))))))
+
+(deftest last-edited-at-correctness-test
+  (let [search-term   "last-edited-at-filtering"
+        new           #t "2023-05-04T10:00Z[UTC]"
+        two-years-ago (t/minus new (t/years 2))]
+    (mt/with-clock new
+      (t2.with-temp/with-temp
+        [:model/Dashboard  {dashboard-new :id} {:name       search-term}
+         :model/Dashboard  {dashboard-old :id} {:name       search-term}
+         :model/Card       {card-new :id}      {:name       search-term}
+         :model/Card       {card-old :id}      {:name       search-term}
+         :model/Card       {model-new :id}     {:name       search-term
+                                                :dataset    true}
+         :model/Card       {model-old :id}     {:name       search-term
+                                                :dataset    true}
+         :model/Metric     {metric-new :id}    {:name       search-term}
+         :model/Metric     {metric-old :id}    {:name       search-term}
+         :model/Action     {action-new :id}    {:name       search-term
+                                                :model_id   model-new
+                                                :type       :http
+                                                :updated_at new}
+         :model/Action     {action-old :id}    {:name       search-term
+                                                :model_id   model-old
+                                                :type       :http
+                                                :updated_at two-years-ago}]
+        (t2/insert! (t2/table-name :model/Revision) (for [[model model-id timestamp]
+                                                          [["Dashboard" dashboard-new new]
+                                                           ["Dashboard" dashboard-old two-years-ago]
+                                                           ["Card" card-new new]
+                                                           ["Card" card-old two-years-ago]
+                                                           ["Card" model-new new]
+                                                           ["Card" model-old two-years-ago]
+                                                           ["Metric" metric-new new]
+                                                           ["Metric" metric-old two-years-ago]]]
+                                                      {:model       model
+                                                       :model_id    model-id
+                                                       :object      "{}"
+                                                       :user_id     (mt/user->id :rasta)
+                                                       :timestamp   timestamp
+                                                       :most_recent true}))
+        ;; with clock doesn't work if calling via API, so we call the search function directly
+        (let [test-search (fn [last-edited-at expected]
+                            (testing (format "searching with last-edited-at = %s" last-edited-at)
+                              (mt/with-current-user (mt/user->id :crowberto)
+                                (is (= expected
+                                       (->> (#'api.search/search (#'api.search/search-context
+                                                                  {:search-string  search-term
+                                                                   :archived       false
+                                                                   :models         search.config/all-models
+                                                                   :last-edited-at last-edited-at}))
+                                            :data
+                                            (map (juxt :model :id))
+                                            set))))))
+              new-result  #{["action"    action-new]
+                            ["card"      card-new]
+                            ["dataset"   model-new]
+                            ["dashboard" dashboard-new]
+                            ["metric"    metric-new]}
+              old-result  #{["action"    action-old]
+                            ["card"      card-old]
+                            ["dataset"   model-old]
+                            ["dashboard" dashboard-old]
+                            ["metric"    metric-old]}]
+          ;; absolute datetime
+          (test-search "Q2-2021" old-result)
+          (test-search "2023-05-04" new-result)
+          (test-search "2021-05-03~" (set/union old-result new-result))
+          ;; range is inclusive of the start but exclusive of the end, so this does not contain new-result
+          (test-search "2021-05-04~2023-05-03" old-result)
+          (test-search "2021-05-05~2023-05-04" new-result)
+          (test-search "~2023-05-03" old-result)
+          (test-search "2021-05-04T09:00:00~2021-05-04T10:00:10" old-result)
+
+          ;; relative times
+          (test-search "thisyear" new-result)
+          (test-search "past1years-from-12months" old-result)
+          (test-search "today" new-result))))))
 
-          (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card"}
-                 (set (mt/user-http-request :crowberto :get 200 "search/models" :q search-term :models "card" :models "dashboard")))))))))
+(deftest available-models-should-be-independent-of-models-param-test
+  (testing "if a search request includes `models` params, the `available_models` from the response should not be restricted by it"
+   (let [search-term "Available models"]
+    (with-search-items-in-root-collection search-term
+      (testing "GET /api/search"
+        (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card" "table" "database"}
+               (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :models "card")
+                   :available_models
+                   set)))
+
+        (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card" "table" "database"}
+               (-> (mt/user-http-request :crowberto :get 200 "search" :q search-term :models "card" :models "dashboard")
+                   :available_models
+                   set))))
+
+      (testing "GET /api/search/models"
+        (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card" "table" "database"}
+               (set (mt/user-http-request :crowberto :get 200 "search/models" :q search-term :models "card"))))
+
+        (is (= #{"dashboard" "dataset" "segment" "collection" "action" "metric" "card" "table" "database"}
+               (set (mt/user-http-request :crowberto :get 200 "search/models" :q search-term :models "card" :models "dashboard")))))))))
+
+(deftest search-native-query-test
+  (let [search-term "search-native-query"]
+    (mt/with-temp
+      [:model/Card {mbql-card :id}             {:name search-term}
+       :model/Card {native-card-in-name :id}   {:name search-term}
+       :model/Card {native-card-in-query :id}  {:dataset_query (mt/native-query {:query (format "select %s" search-term)})}
+       :model/Card {mbql-model :id}            {:name search-term :dataset true}
+       :model/Card {native-model-in-name :id}  {:name search-term :dataset true}
+       :model/Card {native-model-in-query :id} {:dataset_query (mt/native-query {:query (format "select %s" search-term)}) :dataset true}]
+      (mt/with-actions
+        [_                         {:dataset true :dataset_query (mt/mbql-query venues)}
+         {http-action :action-id}  {:type :http :name search-term}
+         {query-action :action-id} {:type :query :dataset_query (mt/native-query {:query (format "delete from %s" search-term)})}]
+        (testing "by default do not search for native content"
+          (is (= #{["card" mbql-card]
+                   ["card" native-card-in-name]
+                   ["dataset" mbql-model]
+                   ["dataset" native-model-in-name]
+                   ["action" http-action]}
+               (->> (mt/user-http-request :crowberto :get 200 "search" :q search-term)
+                    :data
+                    (map (juxt :model :id))
+                    set))))
+
+        (testing "if search-native-query is true, search both dataset_query and the name"
+          (is (= #{["card" mbql-card]
+                   ["card" native-card-in-name]
+                   ["dataset" mbql-model]
+                   ["dataset" native-model-in-name]
+                   ["action" http-action]
+
+                   ["card" native-card-in-query]
+                   ["dataset" native-model-in-query]
+                   ["action" query-action]}
+               (->> (mt/user-http-request :crowberto :get 200 "search" :q search-term :search_native_query true)
+                    :data
+                    (map (juxt :model :id))
+                    set))))))))
+
+(deftest search-result-with-user-metadata-test
+  (let [search-term "with-user-metadata"]
+    (mt/with-temp
+      [:model/User {user-id-1 :id} {:first_name "Ngoc"
+                                    :last_name  "Khuat"}
+       :model/User {user-id-2 :id} {:first_name nil
+                                    :last_name  nil
+                                    :email      "ngoc@metabase.com"}
+       :model/Card {card-id-1 :id} {:creator_id user-id-1
+                                    :name       search-term}
+       :model/Card {card-id-2 :id} {:creator_id user-id-2
+                                    :name       search-term}]
+
+      (revision/push-revision!
+       :entity      :model/Card
+       :id          card-id-1
+       :user-id     user-id-1
+       :is_creation true
+       :object      {:id card-id-1})
+
+      (revision/push-revision!
+       :entity      :model/Card
+       :id          card-id-2
+       :user-id     user-id-2
+       :is_creation true
+       :object      {:id card-id-2})
+
+      (testing "search result should returns creator_common_name and last_editor_common_name"
+        (is (= #{["card" card-id-1 "Ngoc Khuat" "Ngoc Khuat"]
+                 ;; for user that doesn't have first_name or last_name, should fall backs to email
+                 ["card" card-id-2 "ngoc@metabase.com" "ngoc@metabase.com"]}
+               (->> (mt/user-http-request :crowberto :get 200 "search" :q search-term)
+                    :data
+                    (map (juxt :model :id :creator_common_name :last_editor_common_name))
+                    set)))))))
 
 (deftest models-table-db-id-test
   (testing "search/models request includes `table-db-id` param"
@@ -807,12 +1374,9 @@
                      Action      _              (archived {:name     "test action"
                                                            :type     :query
                                                            :model_id model-id})]
-       (testing "`archived-string` is invalid"
-         (is (=? {:message "Invalid input: [\"value must be a valid boolean string ('true' or 'false').\"]"}
-                 (mt/user-http-request :crowberto :get 500 "search/models" :archived-string 1))))
        (testing "`archived-string` is 'false'"
-         (is (= #{"dashboard" "database" "segment" "collection" "action" "metric" "card" "dataset" "table"}
-                (set (mt/user-http-request :crowberto :get 200 "search/models" :archived-string "false")))))
+         (is (= #{"dashboard" "table" "dataset" "segment" "collection" "database" "action" "metric" "card"}
+                (set (mt/user-http-request :crowberto :get 200 "search/models" :archived "false")))))
        (testing "`archived-string` is 'true'"
          (is (= #{"action"}
-                (set (mt/user-http-request :crowberto :get 200 "search/models" :archived-string "true")))))))))
+                (set (mt/user-http-request :crowberto :get 200 "search/models" :archived "true")))))))))
diff --git a/test/metabase/db/custom_migrations_test.clj b/test/metabase/db/custom_migrations_test.clj
index ca66f2690069bee0e402ea79b5f34410191a08c2..d1b1333dc3c71ccd6902b8841cbc94a2637828f5 100644
--- a/test/metabase/db/custom_migrations_test.clj
+++ b/test/metabase/db/custom_migrations_test.clj
@@ -405,13 +405,11 @@
                 {:row 0  :col 0  :size_x 24 :size_y 2}
                 {:row 36 :col 0  :size_x 23 :size_y 1}
                 {:row 36 :col 23 :size_x 1  :size_y 1}]
-               (-> (t2/select-one (t2/table-name :model/Revision) :id revision-id)
-                  :object (json/parse-string true) :cards))))
-     (migrate-down! 46)
-     (testing "downgrade works correctly"
-       (is (= cards
-              (-> (t2/select-one (t2/table-name :model/Revision) :id revision-id)
-                  :object (json/parse-string true) :cards)))))))
+               (t2/select-one-fn (comp :cards :object) :model/Revision :id revision-id))))
+      (migrate-down! 46)
+      (testing "downgrade works correctly"
+        (is (= cards (-> (t2/select-one (t2/table-name :model/Revision) :id revision-id)
+                         :object (json/parse-string true) :cards)))))))
 
 (deftest migrate-dashboard-revision-grid-from-18-to-24-handle-faliure-test
   (impl/test-migrations ["v47.00-032" "v47.00-033"] [migrate!]
@@ -799,22 +797,22 @@
                                                                          :size_y       4
                                                                          :col          1
                                                                          :row          1})]
-        (migrate!)
-        (testing "After the migration, column_settings field refs are updated to include join-alias"
-          (is (= expected
-                 (-> (t2/query-one {:select [:visualization_settings]
-                                    :from   [:report_dashboardcard]
-                                    :where  [:= :id dashcard-id]})
-                     :visualization_settings
-                     json/parse-string))))
-        (db.setup/migrate! db-type data-source :down 46)
-        (testing "After reversing the migration, column_settings field refs are updated to remove join-alias"
-          (is (= visualization-settings
-                 (-> (t2/query-one {:select [:visualization_settings]
-                                    :from   [:report_dashboardcard]
-                                    :where  [:= :id dashcard-id]})
-                     :visualization_settings
-                     json/parse-string))))))))
+       (migrate!)
+       (testing "After the migration, column_settings field refs are updated to include join-alias"
+         (is (= expected
+                (-> (t2/query-one {:select [:visualization_settings]
+                                   :from   [:report_dashboardcard]
+                                   :where  [:= :id dashcard-id]})
+                    :visualization_settings
+                    json/parse-string))))
+       (db.setup/migrate! db-type data-source :down 46)
+       (testing "After reversing the migration, column_settings field refs are updated to remove join-alias"
+         (is (= visualization-settings
+                (-> (t2/query-one {:select [:visualization_settings]
+                                   :from   [:report_dashboardcard]
+                                   :where  [:= :id dashcard-id]})
+                    :visualization_settings
+                    json/parse-string))))))))
 
 (deftest revision-migrate-legacy-dashboard-card-column-settings-field-refs-test
   (testing "Migrations v47.00-045: update dashboard cards' visualization_settings.column_settings legacy field refs"
@@ -939,25 +937,25 @@
                                                    :user_id   user-id
                                                    :object    (json/generate-string dashboard)
                                                    :timestamp :%now})]
-        (migrate!)
-        (testing "column_settings field refs are updated"
-          (is (= expected
-                 (-> (t2/query-one {:select [:object]
-                                    :from   [:revision]
-                                    :where  [:= :id revision-id]})
-                     :object
-                     json/parse-string
-                     (get-in ["cards" 0 "visualization_settings"])))))
-        (db.setup/migrate! db-type data-source :down 46)
-        (testing "down migration restores original visualization_settings, except it's okay if join-alias are missing"
-          (is (= (m/dissoc-in visualization-settings
-                              ["column_settings" (json/generate-string ["ref" ["field" 1 {"join-alias" "Joined table"}]])])
-                 (-> (t2/query-one {:select [:object]
-                                    :from   [:revision]
-                                    :where  [:= :id revision-id]})
-                     :object
-                     json/parse-string
-                     (get-in ["cards" 0 "visualization_settings"])))))))))
+       (migrate!)
+       (testing "column_settings field refs are updated"
+         (is (= expected
+                (-> (t2/query-one {:select [:object]
+                                   :from   [:revision]
+                                   :where  [:= :id revision-id]})
+                    :object
+                    json/parse-string
+                    (get-in ["cards" 0 "visualization_settings"])))))
+       (db.setup/migrate! db-type data-source :down 46)
+       (testing "down migration restores original visualization_settings, except it's okay if join-alias are missing"
+         (is (= (m/dissoc-in visualization-settings
+                             ["column_settings" (json/generate-string ["ref" ["field" 1 {"join-alias" "Joined table"}]])])
+                (-> (t2/query-one {:select [:object]
+                                   :from   [:revision]
+                                   :where  [:= :id revision-id]})
+                    :object
+                    json/parse-string
+                    (get-in ["cards" 0 "visualization_settings"])))))))))
 
 (deftest migrate-database-options-to-database-settings-test
   (let [do-test
diff --git a/test/metabase/db/schema_migrations_test.clj b/test/metabase/db/schema_migrations_test.clj
index 8756cb593d83bc24867b9b13942c207aff67d1f4..5ec2ec79e6b6733aa0d290a3432356b0ffbb1eaf 100644
--- a/test/metabase/db/schema_migrations_test.clj
+++ b/test/metabase/db/schema_migrations_test.clj
@@ -1116,6 +1116,55 @@
         (is (= [{:id 1, :group_id 1, :table_id table-id, :card_id nil, :attribute_remappings "{\"foo\", 1}", :permission_id perm-id}]
                (mdb.query/query {:select [:*] :from [:sandboxes]})))))))
 
+(deftest add-revision-most-recent-test
+  (testing "Migrations v48.00-008-v48.00-009: add `revision.most_recent`"
+    (impl/test-migrations ["v48.00-007" "v48.00-009"] [migrate!]
+      (let [user-id          (:id (create-raw-user! (tu.random/random-email)))
+            old              (t/minus (t/local-date-time) (t/hours 1))
+            rev-dash-1-old (first (t2/insert-returning-pks! (t2/table-name :model/Revision)
+                                                            {:model       "dashboard"
+                                                             :model_id    1
+                                                             :user_id     user-id
+                                                             :object      "{}"
+                                                             :is_creation true
+                                                             :timestamp   old}))
+            rev-dash-1-new (first (t2/insert-returning-pks! (t2/table-name :model/Revision)
+                                                            {:model       "dashboard"
+                                                             :model_id    1
+                                                             :user_id     user-id
+                                                             :object      "{}"
+                                                             :timestamp   :%now}))
+            rev-dash-2-old (first (t2/insert-returning-pks! (t2/table-name :model/Revision)
+                                                            {:model       "dashboard"
+                                                             :model_id    2
+                                                             :user_id     user-id
+                                                             :object      "{}"
+                                                             :is_creation true
+                                                             :timestamp   old}))
+            rev-dash-2-new (first (t2/insert-returning-pks! (t2/table-name :model/Revision)
+                                                            {:model       "dashboard"
+                                                             :model_id    2
+                                                             :user_id     user-id
+                                                             :object      "{}"
+                                                             :timestamp   :%now}))
+            rev-card-1-old (first (t2/insert-returning-pks! (t2/table-name :model/Revision)
+                                                            {:model       "card"
+                                                             :model_id    1
+                                                             :user_id     user-id
+                                                             :object      "{}"
+                                                             :is_creation true
+                                                             :timestamp   old}))
+            rev-card-1-new (first (t2/insert-returning-pks! (t2/table-name :model/Revision)
+                                                            {:model       "card"
+                                                             :model_id    1
+                                                             :user_id     user-id
+                                                             :object      "{}"
+                                                             :timestamp   :%now}))]
+        (migrate!)
+        (is (= #{false} (t2/select-fn-set :most_recent (t2/table-name :model/Revision)
+                                          :id [:in [rev-dash-1-old rev-dash-2-old rev-card-1-old]])))
+        (is (= #{true} (t2/select-fn-set :most_recent (t2/table-name :model/Revision)
+                                         :id [:in [rev-dash-1-new rev-dash-2-new rev-card-1-new]])))))))
 (deftest fks-are-indexed-test
   (mt/test-driver :postgres
     (testing "all FKs should be indexed"
diff --git a/test/metabase/driver/common/parameters/dates_test.clj b/test/metabase/driver/common/parameters/dates_test.clj
index db519bfec06a04b4b8a105239aba7f600c950d7f..9e90ea01b9cee6cc7a5dfc3be6e29d0b93f379f0 100644
--- a/test/metabase/driver/common/parameters/dates_test.clj
+++ b/test/metabase/driver/common/parameters/dates_test.clj
@@ -5,8 +5,7 @@
    [clojure.test.check.generators :as gen]
    [clojure.test.check.properties :as prop]
    [metabase.driver.common.parameters.dates :as params.dates]
-   [metabase.test :as mt]
-   [metabase.util.date-2 :as u.date]))
+   [metabase.test :as mt]))
 
 (deftest ^:parallel date-string->filter-test
   (testing "year and month"
@@ -133,101 +132,313 @@
         (is (thrown? clojure.lang.ExceptionInfo #"Don't know how to parse date string \"exclude-minutes-15-30\""
                (params.dates/date-string->filter "exclude-minutes-15-30" [:field "field" {:base-type :type/DateTime}])))))))
 
-(deftest ^:parallel date-string->range-test
+(defn do-date-string-range-test
+  [s->expected]
   (mt/with-clock #t "2016-06-07T12:13:55Z"
-    (doseq [[group s->expected]
-            {"absolute datetimes"         {"Q1-2016"               {:start "2016-01-01", :end "2016-03-31"}
-                                           "2016-02"               {:start "2016-02-01", :end "2016-02-29"}
-                                           "2016-04-18"            {:start "2016-04-18", :end "2016-04-18"}
-                                           "2016-04-18~2016-04-23" {:start "2016-04-18", :end "2016-04-23"}
-                                           "2016-04-18~"           {:start "2016-04-18"}
-                                           "~2016-04-18"           {:end "2016-04-18"}}
-             "relative (past)"            {"past30seconds"  {:start "2016-06-07T12:13:25", :end "2016-06-07T12:13:54"}
-                                           "past5minutes~"  {:start "2016-06-07T12:08:00", :end "2016-06-07T12:13:00"}
-                                           "past3hours"     {:start "2016-06-07T09:00:00", :end "2016-06-07T11:00:00"}
-                                           "past3days"      {:start "2016-06-04", :end "2016-06-06"}
-                                           "past3days~"     {:start "2016-06-04", :end "2016-06-07"}
-                                           "past7days"      {:start "2016-05-31", :end "2016-06-06"}
-                                           "past30days"     {:start "2016-05-08", :end "2016-06-06"}
-                                           "past2months"    {:start "2016-04-01", :end "2016-05-31"}
-                                           "past2months~"   {:start "2016-04-01", :end "2016-06-30"}
-                                           "past13months"   {:start "2015-05-01", :end "2016-05-31"}
-                                           "past2quarters"  {:start "2015-10-01", :end "2016-03-31"}
-                                           "past2quarters~" {:start "2015-10-01", :end "2016-06-30"}
-                                           "past1years"     {:start "2015-01-01", :end "2015-12-31"}
-                                           "past1years~"    {:start "2015-01-01", :end "2016-12-31"}
-                                           "past16years"    {:start "2000-01-01", :end "2015-12-31"}}
-             "relative (next)"            {"next45seconds"  {:start "2016-06-07T12:13:56", :end "2016-06-07T12:14:40"}
-                                           "next20minutes"  {:start "2016-06-07T12:14:00", :end "2016-06-07T12:33:00"}
-                                           "next6hours"     {:start "2016-06-07T13:00:00", :end "2016-06-07T18:00:00"}
-                                           "next3days"      {:start "2016-06-08", :end "2016-06-10"}
-                                           "next3days~"     {:start "2016-06-07", :end "2016-06-10"}
-                                           "next7days"      {:start "2016-06-08", :end "2016-06-14"}
-                                           "next30days"     {:start "2016-06-08", :end "2016-07-07"}
-                                           "next2months"    {:start "2016-07-01", :end "2016-08-31"}
-                                           "next2months~"   {:start "2016-06-01", :end "2016-08-31"}
-                                           "next2quarters"  {:start "2016-07-01", :end "2016-12-31"}
-                                           "next2quarters~" {:start "2016-04-01", :end "2016-12-31"}
-                                           "next13months"   {:start "2016-07-01", :end "2017-07-31"}
-                                           "next1years"     {:start "2017-01-01", :end "2017-12-31"}
-                                           "next1years~"    {:start "2016-01-01", :end "2017-12-31"}
-                                           "next16years"    {:start "2017-01-01", :end "2032-12-31"}}
-             "relative (this)"            {"thissecond"  {:start "2016-06-07T12:13:55", :end "2016-06-07T12:13:55"}
-                                           "thisminute"  {:start "2016-06-07T12:13:00", :end "2016-06-07T12:13:00"}
-                                           "thishour"    {:start "2016-06-07T12:00:00", :end "2016-06-07T12:00:00"}
-                                           "thisday"     {:start "2016-06-07", :end "2016-06-07"}
-                                           "thisweek"    {:start "2016-06-05", :end "2016-06-11"}
-                                           "thismonth"   {:start "2016-06-01", :end "2016-06-30"}
-                                           "thisquarter" {:start "2016-04-01", :end "2016-06-30"}
-                                           "thisyear"    {:start "2016-01-01", :end "2016-12-31"}}
-             "relative (last)"            {"lastsecond"  {:start "2016-06-07T12:13:54", :end "2016-06-07T12:13:54"}
-                                           "lastminute"  {:start "2016-06-07T12:12:00", :end "2016-06-07T12:12:00"}
-                                           "lasthour"    {:start "2016-06-07T11:00:00", :end "2016-06-07T11:00:00"}
-                                           "lastweek"    {:start "2016-05-29", :end "2016-06-04"}
-                                           "lastmonth"   {:start "2016-05-01", :end "2016-05-31"}
-                                           "lastquarter" {:start "2016-01-01", :end "2016-03-31"}
-                                           "lastyear"    {:start "2015-01-01", :end "2015-12-31"}}
-             "relative (today/yesterday)" {"yesterday" {:start "2016-06-06", :end "2016-06-06"}
-                                           "today"     {:start "2016-06-07", :end "2016-06-07"}}
-             "relative (past) with starting from" {"past1days-from-0days"      {:start "2016-06-06", :end "2016-06-06"}
-                                                   "past1months-from-0months"  {:start "2016-05-01", :end "2016-05-31"}
-                                                   "past1months-from-36months" {:start "2013-05-01", :end "2013-05-31"}
-                                                   "past1years-from-36months"  {:start "2012-01-01", :end "2012-12-31"}
-                                                   "past3days-from-3years"     {:start "2013-06-04", :end "2013-06-06"}}
-             "relative (next) with starting from" {"next2days-from-1months"    {:start "2016-07-08", :end "2016-07-09"}
-                                                   "next1months-from-0months"  {:start "2016-07-01", :end "2016-07-31"}
-                                                   "next1months-from-36months" {:start "2019-07-01", :end "2019-07-31"}
-                                                   "next1years-from-36months"  {:start "2020-01-01", :end "2020-12-31"}
-                                                   "next3days-from-3years"     {:start "2019-06-08", :end "2019-06-10"}
-                                                   "next7hours-from-13months"  {:start "2017-07-07T13:00:00", :end "2017-07-07T19:00:00"}}}]
-      (testing group
-        (doseq [[s inclusive-range]   s->expected
-                [options range-xform] (letfn [(adjust [m k amount]
-                                                (if-not (get m k)
-                                                  m
-                                                  (update m k #(u.date/format (u.date/add (u.date/parse %) :day amount)))))
-                                              (adjust-start [m]
-                                                (adjust m :start -1))
-                                              (adjust-end [m]
-                                                (adjust m :end 1))]
-                                        {nil                                              identity
-                                         {:inclusive-start? false}                        adjust-start
-                                         {:inclusive-end? false}                          adjust-end
-                                         {:inclusive-start? false, :inclusive-end? false} (comp adjust-start adjust-end)})
-                :let                  [expected (range-xform inclusive-range)]]
-          (is (= expected
-                 (params.dates/date-string->range s options))
-              (format "%s with options %s should parse to %s" (pr-str s) (pr-str options) (pr-str expected))))))))
+    (doseq [[s ranges] s->expected
+            [expected-range option] (map vector ranges [nil
+                                                        {:inclusive-start? false}
+                                                        {:inclusive-end? false}
+                                                        {:inclusive-start? false :inclusive-end? false}])]
+      (testing (format "%s with options %s should parse to %s" (pr-str s) (pr-str option) (pr-str ranges))
+        (is (= expected-range
+               (params.dates/date-string->range s option)))))))
+
+
+(deftest ^:parallel date-string->range-absolute-datetimes-test
+  (do-date-string-range-test
+   {"Q1-2016"               [{:start "2016-01-01" :end "2016-03-31"}  ;; inclusive start + end = true (default)
+                             {:start "2015-12-31" :end "2016-03-31"}  ;; inclusive start = false
+                             {:start "2016-01-01" :end "2016-04-01"}  ;; inclusive end   = false
+                             {:start "2015-12-31" :end "2016-04-01"}] ;; inclusive start + end = false
+    "2016-02"               [{:start "2016-02-01" :end "2016-02-29"}
+                             {:start "2016-01-31" :end "2016-02-29"}
+                             {:start "2016-02-01" :end "2016-03-01"}
+                             {:start "2016-01-31" :end "2016-03-01"}]
+    "2016-04-18"            [{:start "2016-04-18" :end "2016-04-18"}
+                             {:start "2016-04-17" :end "2016-04-18"}
+                             {:start "2016-04-18" :end "2016-04-19"}
+                             {:start "2016-04-17" :end "2016-04-19"}]
+    "2016-04-18~2016-04-23" [{:start "2016-04-18" :end "2016-04-23"}
+                             {:start "2016-04-17" :end "2016-04-23"}
+                             {:start "2016-04-18" :end "2016-04-24"}
+                             {:start "2016-04-17" :end "2016-04-24"}]
+    "2016-04-18T10:30:00~2016-04-23T10:30:00" [{:start "2016-04-18T10:30:00" :end "2016-04-23T10:30:00"}
+                                               {:start "2016-04-18T10:29:00" :end "2016-04-23T10:30:00"}
+                                               {:start "2016-04-18T10:30:00" :end "2016-04-23T10:31:00"}
+                                               {:start "2016-04-18T10:29:00" :end "2016-04-23T10:31:00"}]
+
+    "2016-04-18~"           [{:start "2016-04-18"}
+                             {:start "2016-04-17"}
+                             {:start "2016-04-18"}
+                             {:start "2016-04-17"}]
+    "~2016-04-18"           [{:end "2016-04-18"}
+                             {:end "2016-04-18"}
+                             {:end "2016-04-19"}
+                             {:end "2016-04-19"}]}))
+
+(deftest ^:parallel date-string->range-relative-past-test
+  (do-date-string-range-test
+   {"past30seconds"  [{:start "2016-06-07T12:13:25" :end "2016-06-07T12:13:54"}
+                      {:start "2016-06-07T12:13:24" :end "2016-06-07T12:13:54"}
+                      {:start "2016-06-07T12:13:25" :end "2016-06-07T12:13:55"}
+                      {:start "2016-06-07T12:13:24" :end "2016-06-07T12:13:55"}]
+    "past5minutes~"  [{:start "2016-06-07T12:08:00" :end "2016-06-07T12:13:00"}
+                      {:start "2016-06-07T12:07:00" :end "2016-06-07T12:13:00"}
+                      {:start "2016-06-07T12:08:00" :end "2016-06-07T12:14:00"}
+                      {:start "2016-06-07T12:07:00" :end "2016-06-07T12:14:00"}]
+    "past3hours"     [{:start "2016-06-07T09:00:00" :end "2016-06-07T11:00:00"}
+                      {:start "2016-06-07T08:00:00" :end "2016-06-07T11:00:00"}
+                      {:start "2016-06-07T09:00:00" :end "2016-06-07T12:00:00"}
+                      {:start "2016-06-07T08:00:00" :end "2016-06-07T12:00:00"}]
+    "past3days"      [{:start "2016-06-04" :end "2016-06-06"}
+                      {:start "2016-06-03" :end "2016-06-06"}
+                      {:start "2016-06-04" :end "2016-06-07"}
+                      {:start "2016-06-03" :end "2016-06-07"}]
+    "past3days~"     [{:start "2016-06-04" :end "2016-06-07"}
+                      {:start "2016-06-03" :end "2016-06-07"}
+                      {:start "2016-06-04" :end "2016-06-08"}
+                      {:start "2016-06-03" :end "2016-06-08"}]
+    "past7days"      [{:start "2016-05-31" :end "2016-06-06"}
+                      {:start "2016-05-30" :end "2016-06-06"}
+                      {:start "2016-05-31" :end "2016-06-07"}
+                      {:start "2016-05-30" :end "2016-06-07"}]
+    "past30days"     [{:start "2016-05-08" :end "2016-06-06"}
+                      {:start "2016-05-07" :end "2016-06-06"}
+                      {:start "2016-05-08" :end "2016-06-07"}
+                      {:start "2016-05-07" :end "2016-06-07"}]
+    "past2months"    [{:start "2016-04-01" :end "2016-05-31"}
+                      {:start "2016-03-31" :end "2016-05-31"}
+                      {:start "2016-04-01" :end "2016-06-01"}
+                      {:start "2016-03-31" :end "2016-06-01"}]
+    "past2months~"   [{:start "2016-04-01" :end "2016-06-30"}
+                      {:start "2016-03-31" :end "2016-06-30"}
+                      {:start "2016-04-01" :end "2016-07-01"}
+                      {:start "2016-03-31" :end "2016-07-01"}]
+    "past13months"   [{:start "2015-05-01" :end "2016-05-31"}
+                      {:start "2015-04-30" :end "2016-05-31"}
+                      {:start "2015-05-01" :end "2016-06-01"}
+                      {:start "2015-04-30" :end "2016-06-01"}]
+    "past2quarters"  [{:start "2015-10-01" :end "2016-03-31"}
+                      {:start "2015-09-30" :end "2016-03-31"}
+                      {:start "2015-10-01" :end "2016-04-01"}
+                      {:start "2015-09-30" :end "2016-04-01"}]
+    "past2quarters~" [{:start "2015-10-01" :end "2016-06-30"}
+                      {:start "2015-09-30" :end "2016-06-30"}
+                      {:start "2015-10-01" :end "2016-07-01"}
+                      {:start "2015-09-30" :end "2016-07-01"}]
+    "past1years"     [{:start "2015-01-01" :end "2015-12-31"}
+                      {:start "2014-12-31" :end "2015-12-31"}
+                      {:start "2015-01-01" :end "2016-01-01"}
+                      {:start "2014-12-31" :end "2016-01-01"}]
+    "past1years~"    [{:start "2015-01-01" :end "2016-12-31"}
+                      {:start "2014-12-31" :end "2016-12-31"}
+                      {:start "2015-01-01" :end "2017-01-01"}
+                      {:start "2014-12-31" :end "2017-01-01"}]
+    "past16years"    [{:start "2000-01-01" :end "2015-12-31"}
+                      {:start "1999-12-31" :end "2015-12-31"}
+                      {:start "2000-01-01" :end "2016-01-01"}
+                      {:start "1999-12-31" :end "2016-01-01"}]}))
+
+(deftest ^:parallel date-string->range-relative-next-test
+  (do-date-string-range-test
+   {"next45seconds"  [{:start "2016-06-07T12:13:56" :end "2016-06-07T12:14:40"}
+                      {:start "2016-06-07T12:13:55" :end "2016-06-07T12:14:40"}
+                      {:start "2016-06-07T12:13:56" :end "2016-06-07T12:14:41"}
+                      {:start "2016-06-07T12:13:55" :end "2016-06-07T12:14:41"}]
+    "next20minutes"  [{:start "2016-06-07T12:14:00" :end "2016-06-07T12:33:00"}
+                      {:start "2016-06-07T12:13:00" :end "2016-06-07T12:33:00"}
+                      {:start "2016-06-07T12:14:00" :end "2016-06-07T12:34:00"}
+                      {:start "2016-06-07T12:13:00" :end "2016-06-07T12:34:00"}]
+    "next6hours"     [{:start "2016-06-07T13:00:00" :end "2016-06-07T18:00:00"}
+                      {:start "2016-06-07T12:00:00" :end "2016-06-07T18:00:00"}
+                      {:start "2016-06-07T13:00:00" :end "2016-06-07T19:00:00"}
+                      {:start "2016-06-07T12:00:00" :end "2016-06-07T19:00:00"}]
+    "next3days"      [{:start "2016-06-08" :end "2016-06-10"}
+                      {:start "2016-06-07" :end "2016-06-10"}
+                      {:start "2016-06-08" :end "2016-06-11"}
+                      {:start "2016-06-07" :end "2016-06-11"}]
+    "next3days~"     [{:start "2016-06-07" :end "2016-06-10"}
+                      {:start "2016-06-06" :end "2016-06-10"}
+                      {:start "2016-06-07" :end "2016-06-11"}
+                      {:start "2016-06-06" :end "2016-06-11"}]
+    "next7days"      [{:start "2016-06-08" :end "2016-06-14"}
+                      {:start "2016-06-07" :end "2016-06-14"}
+                      {:start "2016-06-08" :end "2016-06-15"}
+                      {:start "2016-06-07" :end "2016-06-15"}]
+    "next30days"     [{:start "2016-06-08" :end "2016-07-07"}
+                      {:start "2016-06-07" :end "2016-07-07"}
+                      {:start "2016-06-08" :end "2016-07-08"}
+                      {:start "2016-06-07" :end "2016-07-08"}]
+    "next2months"    [{:start "2016-07-01" :end "2016-08-31"}
+                      {:start "2016-06-30" :end "2016-08-31"}
+                      {:start "2016-07-01" :end "2016-09-01"}
+                      {:start "2016-06-30" :end "2016-09-01"}]
+    "next2months~"   [{:start "2016-06-01" :end "2016-08-31"}
+                      {:start "2016-05-31" :end "2016-08-31"}
+                      {:start "2016-06-01" :end "2016-09-01"}
+                      {:start "2016-05-31" :end "2016-09-01"}]
+    "next2quarters"  [{:start "2016-07-01" :end "2016-12-31"}
+                      {:start "2016-06-30" :end "2016-12-31"}
+                      {:start "2016-07-01" :end "2017-01-01"}
+                      {:start "2016-06-30" :end "2017-01-01"}]
+    "next2quarters~" [{:start "2016-04-01" :end "2016-12-31"}
+                      {:start "2016-03-31" :end "2016-12-31"}
+                      {:start "2016-04-01" :end "2017-01-01"}
+                      {:start "2016-03-31" :end "2017-01-01"}]
+    "next13months"   [{:start "2016-07-01" :end "2017-07-31"}
+                      {:start "2016-06-30" :end "2017-07-31"}
+                      {:start "2016-07-01" :end "2017-08-01"}
+                      {:start "2016-06-30" :end "2017-08-01"}]
+    "next1years"     [{:start "2017-01-01" :end "2017-12-31"}
+                      {:start "2016-12-31" :end "2017-12-31"}
+                      {:start "2017-01-01" :end "2018-01-01"}
+                      {:start "2016-12-31" :end "2018-01-01"}]
+    "next1years~"    [{:start "2016-01-01" :end "2017-12-31"}
+                      {:start "2015-12-31" :end "2017-12-31"}
+                      {:start "2016-01-01" :end "2018-01-01"}
+                      {:start "2015-12-31" :end "2018-01-01"}]
+    "next16years"    [{:start "2017-01-01" :end "2032-12-31"}
+                      {:start "2016-12-31" :end "2032-12-31"}
+                      {:start "2017-01-01" :end "2033-01-01"}
+                      {:start "2016-12-31" :end "2033-01-01"}]}))
+
+(deftest ^:parallel date-string->range-relative-this-test
+  (do-date-string-range-test
+   {"thissecond"  [{:start "2016-06-07T12:13:55" :end "2016-06-07T12:13:55"}
+                   {:start "2016-06-07T12:13:54" :end "2016-06-07T12:13:55"}
+                   {:start "2016-06-07T12:13:55" :end "2016-06-07T12:13:56"}
+                   {:start "2016-06-07T12:13:54" :end "2016-06-07T12:13:56"}]
+    "thisminute"  [{:start "2016-06-07T12:13:00" :end "2016-06-07T12:13:00"}
+                   {:start "2016-06-07T12:12:00" :end "2016-06-07T12:13:00"}
+                   {:start "2016-06-07T12:13:00" :end "2016-06-07T12:14:00"}
+                   {:start "2016-06-07T12:12:00" :end "2016-06-07T12:14:00"}]
+    "thishour"    [{:start "2016-06-07T12:00:00" :end "2016-06-07T12:00:00"}
+                   {:start "2016-06-07T11:00:00" :end "2016-06-07T12:00:00"}
+                   {:start "2016-06-07T12:00:00" :end "2016-06-07T13:00:00"}
+                   {:start "2016-06-07T11:00:00" :end "2016-06-07T13:00:00"}]
+    "thisday"     [{:start "2016-06-07" :end "2016-06-07"}
+                   {:start "2016-06-06" :end "2016-06-07"}
+                   {:start "2016-06-07" :end "2016-06-08"}
+                   {:start "2016-06-06" :end "2016-06-08"}]
+    "thisweek"    [{:start "2016-06-05" :end "2016-06-11"}
+                   {:start "2016-06-04" :end "2016-06-11"}
+                   {:start "2016-06-05" :end "2016-06-12"}
+                   {:start "2016-06-04" :end "2016-06-12"}]
+    "thismonth"   [{:start "2016-06-01" :end "2016-06-30"}
+                   {:start "2016-05-31" :end "2016-06-30"}
+                   {:start "2016-06-01" :end "2016-07-01"}
+                   {:start "2016-05-31" :end "2016-07-01"}]
+    "thisquarter" [{:start "2016-04-01" :end "2016-06-30"}
+                   {:start "2016-03-31" :end "2016-06-30"}
+                   {:start "2016-04-01" :end "2016-07-01"}
+                   {:start "2016-03-31" :end "2016-07-01"}]
+    "thisyear"    [{:start "2016-01-01" :end "2016-12-31"}
+                   {:start "2015-12-31" :end "2016-12-31"}
+                   {:start "2016-01-01" :end "2017-01-01"}
+                   {:start "2015-12-31" :end "2017-01-01"}]}))
+
+(deftest ^:parallel date-string->range-relative-last-test
+  (do-date-string-range-test
+   {"lastsecond"  [{:start "2016-06-07T12:13:54" :end "2016-06-07T12:13:54"}
+                   {:start "2016-06-07T12:13:53" :end "2016-06-07T12:13:54"}
+                   {:start "2016-06-07T12:13:54" :end "2016-06-07T12:13:55"}
+                   {:start "2016-06-07T12:13:53" :end "2016-06-07T12:13:55"}]
+    "lastminute"  [{:start "2016-06-07T12:12:00" :end "2016-06-07T12:12:00"}
+                   {:start "2016-06-07T12:11:00" :end "2016-06-07T12:12:00"}
+                   {:start "2016-06-07T12:12:00" :end "2016-06-07T12:13:00"}
+                   {:start "2016-06-07T12:11:00" :end "2016-06-07T12:13:00"}]
+    "lasthour"    [{:start "2016-06-07T11:00:00" :end "2016-06-07T11:00:00"}
+                   {:start "2016-06-07T10:00:00" :end "2016-06-07T11:00:00"}
+                   {:start "2016-06-07T11:00:00" :end "2016-06-07T12:00:00"}
+                   {:start "2016-06-07T10:00:00" :end "2016-06-07T12:00:00"}]
+    "lastweek"    [{:start "2016-05-29" :end "2016-06-04"}
+                   {:start "2016-05-28" :end "2016-06-04"}
+                   {:start "2016-05-29" :end "2016-06-05"}
+                   {:start "2016-05-28" :end "2016-06-05"}]
+    "lastmonth"   [{:start "2016-05-01" :end "2016-05-31"}
+                   {:start "2016-04-30" :end "2016-05-31"}
+                   {:start "2016-05-01" :end "2016-06-01"}
+                   {:start "2016-04-30" :end "2016-06-01"}]
+    "lastquarter" [{:start "2016-01-01" :end "2016-03-31"}
+                   {:start "2015-12-31" :end "2016-03-31"}
+                   {:start "2016-01-01" :end "2016-04-01"}
+                   {:start "2015-12-31" :end "2016-04-01"}]
+    "lastyear"    [{:start "2015-01-01" :end "2015-12-31"}
+                   {:start "2014-12-31" :end "2015-12-31"}
+                   {:start "2015-01-01" :end "2016-01-01"}
+                   {:start "2014-12-31" :end "2016-01-01"}]}))
+
+(deftest ^:parallel date-string->range-relative-today-yesterday-test
+  (do-date-string-range-test
+   {"yesterday" [{:start "2016-06-06" :end "2016-06-06"}
+                 {:start "2016-06-05" :end "2016-06-06"}
+                 {:start "2016-06-06" :end "2016-06-07"}
+                 {:start "2016-06-05" :end "2016-06-07"}]
+    "today"     [{:start "2016-06-07" :end "2016-06-07"}
+                 {:start "2016-06-06" :end "2016-06-07"}
+                 {:start "2016-06-07" :end "2016-06-08"}
+                 {:start "2016-06-06" :end "2016-06-08"}]}))
+
+(deftest ^:parallel date-string->range-relative-past-from-test
+  (do-date-string-range-test
+   {"past1days-from-0days"      [{:start "2016-06-06" :end "2016-06-06"}
+                                 {:start "2016-06-05" :end "2016-06-06"}
+                                 {:start "2016-06-06" :end "2016-06-07"}
+                                 {:start "2016-06-05" :end "2016-06-07"}]
+    "past1months-from-0months"  [{:start "2016-05-01" :end "2016-05-31"}
+                                 {:start "2016-04-30" :end "2016-05-31"}
+                                 {:start "2016-05-01" :end "2016-06-01"}
+                                 {:start "2016-04-30" :end "2016-06-01"}]
+    "past1months-from-36months" [{:start "2013-05-01" :end "2013-05-31"}
+                                 {:start "2013-04-30" :end "2013-05-31"}
+                                 {:start "2013-05-01" :end "2013-06-01"}
+                                 {:start "2013-04-30" :end "2013-06-01"}]
+    "past1years-from-36months"  [{:start "2012-01-01" :end "2012-12-31"}
+                                 {:start "2011-12-31" :end "2012-12-31"}
+                                 {:start "2012-01-01" :end "2013-01-01"}
+                                 {:start "2011-12-31" :end "2013-01-01"}]
+    "past3days-from-3years"     [{:start "2013-06-04" :end "2013-06-06"}
+                                 {:start "2013-06-03" :end "2013-06-06"}
+                                 {:start "2013-06-04" :end "2013-06-07"}
+                                 {:start "2013-06-03" :end "2013-06-07"}]}))
+
+(deftest ^:parallel date-string->range-relative-next-from-test
+  (do-date-string-range-test
+   {"next2days-from-1months"    [{:start "2016-07-08" :end "2016-07-09"}
+                                 {:start "2016-07-07" :end "2016-07-09"}
+                                 {:start "2016-07-08" :end "2016-07-10"}
+                                 {:start "2016-07-07" :end "2016-07-10"}]
+    "next1months-from-0months"  [{:start "2016-07-01" :end "2016-07-31"}
+                                 {:start "2016-06-30" :end "2016-07-31"}
+                                 {:start "2016-07-01" :end "2016-08-01"}
+                                 {:start "2016-06-30" :end "2016-08-01"}]
+    "next1months-from-36months" [{:start "2019-07-01" :end "2019-07-31"}
+                                 {:start "2019-06-30" :end "2019-07-31"}
+                                 {:start "2019-07-01" :end "2019-08-01"}
+                                 {:start "2019-06-30" :end "2019-08-01"}]
+    "next1years-from-36months"  [{:start "2020-01-01" :end "2020-12-31"}
+                                 {:start "2019-12-31" :end "2020-12-31"}
+                                 {:start "2020-01-01" :end "2021-01-01"}
+                                 {:start "2019-12-31" :end "2021-01-01"}]
+    "next3days-from-3years"     [{:start "2019-06-08" :end "2019-06-10"}
+                                 {:start "2019-06-07" :end "2019-06-10"}
+                                 {:start "2019-06-08" :end "2019-06-11"}
+                                 {:start "2019-06-07" :end "2019-06-11"}]
+    "next7hours-from-13months"  [{:start "2017-07-07T13:00:00" :end "2017-07-07T19:00:00"}
+                                 {:start "2017-07-07T12:00:00" :end "2017-07-07T19:00:00"}
+                                 {:start "2017-07-07T13:00:00" :end "2017-07-07T20:00:00"}
+                                 {:start "2017-07-07T12:00:00" :end "2017-07-07T20:00:00"}]}))
 
 (deftest ^:parallel relative-dates-with-starting-from-zero-must-match
   (testing "relative dates need to behave the same way, offset or not."
     (mt/with-clock #t "2016-06-07T12:13:55Z"
       (testing "'past1months-from-0months' should be the same as: 'past1months'"
-        (is (= {:start "2016-05-01", :end "2016-05-31"}
+        (is (= {:start "2016-05-01" :end "2016-05-31"}
                (params.dates/date-string->range "past1months")
                (params.dates/date-string->range "past1months-from-0months"))))
       (testing "'next1months-from-0months' should be the same as: 'next1months'"
-        (is (= {:start "2016-07-01", :end "2016-07-31"}
+        (is (= {:start "2016-07-01" :end "2016-07-31"}
                (params.dates/date-string->range "next1months")
                (params.dates/date-string->range "next1months-from-0months")))))))
 
@@ -251,13 +462,13 @@
 (deftest custom-start-of-week-test
   (testing "Relative filters should respect the custom `start-of-week` Setting (#14294)"
     (mt/with-clock #t "2021-03-01T14:15:00-08:00[US/Pacific]"
-      (doseq [[first-day-of-week expected] {"sunday"    {:start "2021-02-21", :end "2021-02-27"}
-                                            "monday"    {:start "2021-02-22", :end "2021-02-28"}
-                                            "tuesday"   {:start "2021-02-16", :end "2021-02-22"}
-                                            "wednesday" {:start "2021-02-17", :end "2021-02-23"}
-                                            "thursday"  {:start "2021-02-18", :end "2021-02-24"}
-                                            "friday"    {:start "2021-02-19", :end "2021-02-25"}
-                                            "saturday"  {:start "2021-02-20", :end "2021-02-26"}}]
+      (doseq [[first-day-of-week expected] {"sunday"    {:start "2021-02-21" :end "2021-02-27"}
+                                            "monday"    {:start "2021-02-22" :end "2021-02-28"}
+                                            "tuesday"   {:start "2021-02-16" :end "2021-02-22"}
+                                            "wednesday" {:start "2021-02-17" :end "2021-02-23"}
+                                            "thursday"  {:start "2021-02-18" :end "2021-02-24"}
+                                            "friday"    {:start "2021-02-19" :end "2021-02-25"}
+                                            "saturday"  {:start "2021-02-20" :end "2021-02-26"}}]
         (mt/with-temporary-setting-values [start-of-week first-day-of-week]
           (is (= expected
                  (params.dates/date-string->range "past1weeks"))))))))
diff --git a/test/metabase/models/revision_test.clj b/test/metabase/models/revision_test.clj
index 8ca1788dd2189f1dd53c29ee9b064e0a00e01db2..352fe1a5fedaa539fd7f082a0452695f13809456 100644
--- a/test/metabase/models/revision_test.clj
+++ b/test/metabase/models/revision_test.clj
@@ -108,240 +108,272 @@
              (revision/revisions ::FakedCard card-id))))))
 
 (deftest add-revision-test
-  (testing "Test that we can add a revision"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Tips Created by Day", :message "yay!")
-      (is (= [(mi/instance
-               Revision
-               {:model            "FakedCard"
-                :user_id          (mt/user->id :rasta)
-                :object           (mi/instance 'FakedCard {:name "Tips Created by Day", :serialized true})
-                :is_reversion     false
-                :is_creation      false
-                :message          "yay!"
-                :metabase_version config/mb-version-string})]
-             (for [revision (revision/revisions ::FakedCard card-id)]
-               (dissoc revision :timestamp :id :model_id)))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Test that we can add a revision"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Tips Created by Day", :message "yay!")
+        (is (=? [(mi/instance
+                  Revision
+                  {:model        "FakedCard"
+                   :user_id      (mt/user->id :rasta)
+                   :object       (mi/instance ::FakedCard {:name "Tips Created by Day", :serialized true})
+                   :is_reversion false
+                   :is_creation  false
+                   :message      "yay!"})]
+                (for [revision (revision/revisions ::FakedCard card-id)]
+                  (dissoc revision :timestamp :id :model_id))))))
+
+    (testing "test that most_recent is correct"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (doseq [i (range 3)]
+          (push-fake-revision! card-id :name (format "%d Tips Created by Day" i) :message "yay!"))
+       (is (=? [{:model       "FakedCard"
+                 :model_id    card-id
+                 :most_recent true}
+                {:model       "FakedCard"
+                 :model_id    card-id
+                 :most_recent false}
+                {:model       "FakedCard"
+                 :model_id    card-id
+                 :most_recent false}]
+               (t2/select :model/Revision :model "FakedCard" :model_id card-id {:order-by [[:timestamp :desc] [:id :desc]]})))))))
+
+(deftest update-revision-does-not-update-timestamp-test
+  ;; Realistically this only happens on mysql and mariadb for some reasons
+  ;; and we can't update revision anyway, except for when we need to change most_recent
+  (t2.with-temp/with-temp [Card {card-id :id} {}]
+    (let [revision (first (t2/insert-returning-instances! :model/Revision {:model       "Card"
+                                                                           :user_id     (mt/user->id :crowberto)
+                                                                           :model_id    card-id
+                                                                           :object      {}
+                                                                           :most_recent false}))]
+      (t2/update! (t2/table-name :model/Revision) (:id revision) {:most_recent true})
+      (is (= (:timestamp revision)
+             (t2/select-one-fn :timestamp :model/Revision (:id revision)))))))
 
 (deftest sorting-test
   (testing "Test that revisions are sorted in reverse chronological order"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Tips Created by Day")
-      (push-fake-revision! card-id, :name "Spots Created by Day")
-      (testing `revision/revisions
-        (is (= [(mi/instance
-                 Revision
-                 {:model            "FakedCard"
-                  :user_id          (mt/user->id :rasta)
-                  :object           (mi/instance 'FakedCard {:name "Spots Created by Day", :serialized true})
-                  :is_reversion     false
-                  :is_creation      false
-                  :message          nil
-                  :metabase_version config/mb-version-string})
-                (mi/instance
-                 Revision
-                 {:model            "FakedCard"
-                  :user_id          (mt/user->id :rasta)
-                  :object           (mi/instance 'FakedCard {:name "Tips Created by Day", :serialized true})
-                  :is_reversion     false
-                  :is_creation      false
-                  :message          nil
-                  :metabase_version config/mb-version-string})]
-               (->> (revision/revisions ::FakedCard card-id)
-                    (map #(dissoc % :timestamp :id :model_id)))))))))
+    (mt/with-model-cleanup [:model/Revision]
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Tips Created by Day")
+        (push-fake-revision! card-id, :name "Spots Created by Day")
+        (testing "revision/revisions"
+          (is (=? [(mi/instance
+                    Revision
+                    {:model        "FakedCard"
+                     :user_id      (mt/user->id :rasta)
+                     :object       (mi/instance ::FakedCard {:name "Spots Created by Day", :serialized true})
+                     :is_reversion false
+                     :is_creation  false
+                     :message      nil})
+                   (mi/instance
+                    Revision
+                    {:model        "FakedCard"
+                     :user_id      (mt/user->id :rasta)
+                     :object       (mi/instance ::FakedCard {:name "Tips Created by Day", :serialized true})
+                     :is_reversion false
+                     :is_creation  false
+                     :message      nil})]
+                  (->> (revision/revisions ::FakedCard card-id)
+                       (map #(dissoc % :timestamp :id :model_id))))))))))
 
 (deftest delete-old-revisions-test
   (testing "Check that old revisions get deleted"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      ;; e.g. if max-revisions is 15 then insert 16 revisions
-      (dorun (doseq [i (range (inc revision/max-revisions))]
-               (push-fake-revision! card-id, :name (format "Tips Created by Day %d" i))))
-      (is (= revision/max-revisions
-             (count (revision/revisions ::FakedCard card-id)))))))
+    (mt/with-model-cleanup [:model/Revision]
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        ;; e.g. if max-revisions is 15 then insert 16 revisions
+        (dorun (doseq [i (range (inc revision/max-revisions))]
+                 (push-fake-revision! card-id, :name (format "Tips Created by Day %d" i))))
+        (is (= revision/max-revisions
+               (count (revision/revisions ::FakedCard card-id))))))))
 
 (deftest do-not-record-if-object-is-not-changed-test
-  (testing "Check that we don't record a revision if the object hasn't changed"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (let [new-revision (fn [x]
-                           (push-fake-revision! card-id, :name (format "Tips Created by Day %s" x)))]
-        (testing "first revision should be recorded"
-          (new-revision 1)
-          (is (= 1 (count (revision/revisions ::FakedCard card-id)))))
-
-        (testing "repeatedly push reivisions with the same object shouldn't create new revision"
-          (dorun (repeatedly 5 #(new-revision 1)))
-          (is (= 1 (count (revision/revisions ::FakedCard card-id)))))
-
-        (testing "push a revision with different object should create new revision"
-          (new-revision 2)
-          (is (= 2 (count (revision/revisions ::FakedCard card-id))))))))
-
-  (testing "Check that we don't record revision on dashboard if it has a filter"
-    (t2.with-temp/with-temp
-      [:model/Dashboard     {dash-id :id} {:parameters [{:name "Category Name"
-                                                         :slug "category_name"
-                                                         :id   "_CATEGORY_NAME_"
-                                                         :type "category"}]}
-       :model/Card          {card-id :id} {}
-       :model/DashboardCard {}            {:dashboard_id       dash-id
-                                           :card_id            card-id
-                                           :parameter_mappings [{:parameter_id "_CATEGORY_NAME_"
-                                                                 :card_id      card-id
-                                                                 :target       [:dimension (mt/$ids $categories.name)]}]}]
-      (let [push-revision (fn [] (revision/push-revision!
-                                   :entity :model/Dashboard
-                                   :id     dash-id
-                                   :user-id (mt/user->id :rasta)
-                                   :object (t2/select-one :model/Dashboard dash-id)))]
-        (testing "first revision should be recorded"
-          (push-revision)
-          (is (= 1 (count (revision/revisions :model/Dashboard dash-id)))))
-        (testing "push again without changes shouldn't record new revision"
-          (push-revision)
-          (is (= 1 (count (revision/revisions :model/Dashboard dash-id)))))
-        (testing "now do some updates and new revision should be reocrded"
-          (t2/update! :model/Dashboard :id dash-id {:name "New name"})
-          (push-revision)
-          (is (= 2 (count (revision/revisions :model/Dashboard dash-id)))))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Check that we don't record a revision if the object hasn't changed"
+      (mt/with-model-cleanup [:model/Revision]
+        (t2.with-temp/with-temp [Card {card-id :id}]
+          (let [new-revision (fn [x]
+                               (push-fake-revision! card-id, :name (format "Tips Created by Day %s" x)))]
+            (testing "first revision should be recorded"
+              (new-revision 1)
+              (is (= 1 (count (revision/revisions ::FakedCard card-id)))))
+
+            (testing "repeatedly push reivisions with the same object shouldn't create new revision"
+              (dorun (repeatedly 5 #(new-revision 1)))
+              (is (= 1 (count (revision/revisions ::FakedCard card-id)))))
+
+            (testing "push a revision with different object should create new revision"
+              (new-revision 2)
+              (is (= 2 (count (revision/revisions ::FakedCard card-id))))))))
+
+      (testing "Check that we don't record revision on dashboard if it has a filter"
+        (t2.with-temp/with-temp
+          [:model/Dashboard     {dash-id :id} {:parameters [{:name "Category Name"
+                                                             :slug "category_name"
+                                                             :id   "_CATEGORY_NAME_"
+                                                             :type "category"}]}
+           :model/Card          {card-id :id} {}
+           :model/DashboardCard {}            {:dashboard_id       dash-id
+                                               :card_id            card-id
+                                               :parameter_mappings [{:parameter_id "_CATEGORY_NAME_"
+                                                                     :card_id      card-id
+                                                                     :target       [:dimension (mt/$ids $categories.name)]}]}]
+          (let [push-revision (fn [] (revision/push-revision!
+                                      :entity :model/Dashboard
+                                      :id     dash-id
+                                      :user-id (mt/user->id :rasta)
+                                      :object (t2/select-one :model/Dashboard dash-id)))]
+            (testing "first revision should be recorded"
+              (push-revision)
+              (is (= 1 (count (revision/revisions :model/Dashboard dash-id)))))
+            (testing "push again without changes shouldn't record new revision"
+              (push-revision)
+              (is (= 1 (count (revision/revisions :model/Dashboard dash-id)))))
+            (testing "now do some updates and new revision should be reocrded"
+              (t2/update! :model/Dashboard :id dash-id {:name "New name"})
+              (push-revision)
+              (is (= 2 (count (revision/revisions :model/Dashboard dash-id)))))))))))
 
 ;;; # REVISIONS+DETAILS
 
 (deftest add-revision-details-test
-  (testing "Test that add-revision-details properly enriches our revision objects"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Initial Name")
-      (push-fake-revision! card-id, :name "Modified Name")
-      (is (= {:is_creation          false
-              :is_reversion         false
-              :message              nil
-              :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"}
-              :diff                 {:o1 {:name "Initial Name", :serialized true}
-                                     :o2 {:name "Modified Name", :serialized true}}
-              :has_multiple_changes false
-              :description          "BEFORE={:name \"Initial Name\", :serialized true},AFTER={:name \"Modified Name\", :serialized true}."
-              :metabase_version     config/mb-version-string}
-             (let [revisions (revision/revisions ::FakedCard card-id)]
-               (assert (= 2 (count revisions)))
-               (-> (revision/add-revision-details ::FakedCard (first revisions) (last revisions))
-                   (dissoc :timestamp :id :model_id)
-                   mt/derecordize))))))
-
-  (testing "test that we return a description even when there is no change between revision"
-    (is (= "created a revision with no change."
-           (str (:description (revision/add-revision-details ::FakedCard {:name "Apple"} {:name "Apple"}))))))
-
-  (testing "that we return a descrtiopn when there is no previous revision"
-    (is (= "modified this."
-           (str (:description (revision/add-revision-details ::FakedCard {:name "Apple"} nil)))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Test that add-revision-details properly enriches our revision objects"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Initial Name")
+        (push-fake-revision! card-id, :name "Modified Name")
+        (is (=? {:is_creation          false
+                 :is_reversion         false
+                 :message              nil
+                 :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"}
+                 :diff                 {:o1 {:name "Initial Name", :serialized true}
+                                        :o2 {:name "Modified Name", :serialized true}}
+                 :has_multiple_changes false
+                 :description          "BEFORE={:name \"Initial Name\", :serialized true},AFTER={:name \"Modified Name\", :serialized true}."}
+                (let [revisions (revision/revisions ::FakedCard card-id)]
+                  (assert (= 2 (count revisions)))
+                  (-> (revision/add-revision-details ::FakedCard (first revisions) (last revisions))
+                      (dissoc :timestamp :id :model_id)
+                      mt/derecordize))))))
+
+    (testing "test that we return a description even when there is no change between revision"
+      (is (= "created a revision with no change."
+             (str (:description (revision/add-revision-details ::FakedCard {:name "Apple"} {:name "Apple"}))))))
+
+    (testing "that we return a descrtiopn when there is no previous revision"
+      (is (= "modified this."
+             (str (:description (revision/add-revision-details ::FakedCard {:name "Apple"} nil))))))))
 
 (deftest revisions+details-test
-  (testing "Check that revisions+details pulls in user info and adds description"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Tips Created by Day")
-      (is (= [(mi/instance
-               Revision
-               {:is_reversion         false,
-                :is_creation          false,
-                :message              nil,
-                :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"},
-                :diff                 {:o1 nil
-                                       :o2 {:name "Tips Created by Day", :serialized true}}
-                :has_multiple_changes false
-                :description          "modified this."
-                :metabase_version     config/mb-version-string})]
-             (->> (revision/revisions+details ::FakedCard card-id)
-                  (map #(dissoc % :timestamp :id :model_id))
-                  (map #(update % :description str))))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Check that revisions+details pulls in user info and adds description"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Tips Created by Day")
+        (is (=? [(mi/instance
+                  Revision
+                  {:is_reversion         false,
+                   :is_creation          false,
+                   :message              nil,
+                   :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"},
+                   :diff                 {:o1 nil
+                                          :o2 {:name "Tips Created by Day", :serialized true}}
+                   :has_multiple_changes false
+                   :description          "modified this."})]
+                (->> (revision/revisions+details ::FakedCard card-id)
+                     (map #(dissoc % :timestamp :id :model_id))
+                     (map #(update % :description str)))))))))
 
 (deftest defer-to-describe-diff-test
-  (testing "Check that revisions properly defer to describe-diff"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Tips Created by Day")
-      (push-fake-revision! card-id, :name "Spots Created by Day")
-      (is (= [(mi/instance
-               Revision
-               {:is_reversion         false,
-                :is_creation          false,
-                :message              nil
-                :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"},
-                :diff                 {:o1 {:name "Tips Created by Day", :serialized true}
-                                       :o2 {:name "Spots Created by Day", :serialized true}}
-                :has_multiple_changes false
-                :description          (str "BEFORE={:name \"Tips Created by Day\", :serialized true},AFTER="
-                                           "{:name \"Spots Created by Day\", :serialized true}.")
-                :metabase_version     config/mb-version-string})
-              (mi/instance
-               Revision
-               {:is_reversion         false,
-                :is_creation          false,
-                :message              nil
-                :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"},
-                :diff                 {:o1 nil
-                                       :o2 {:name "Tips Created by Day", :serialized true}}
-                :has_multiple_changes false
-                :description          "modified this."
-                :metabase_version     config/mb-version-string})]
-             (->> (revision/revisions+details ::FakedCard card-id)
-                  (map #(dissoc % :timestamp :id :model_id))
-                  (map #(update % :description str))))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Check that revisions properly defer to describe-diff"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Tips Created by Day")
+        (push-fake-revision! card-id, :name "Spots Created by Day")
+        (is (=? [(mi/instance
+                  Revision
+                  {:is_reversion         false,
+                   :is_creation          false,
+                   :message              nil
+                   :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"},
+                   :diff                 {:o1 {:name "Tips Created by Day", :serialized true}
+                                          :o2 {:name "Spots Created by Day", :serialized true}}
+                   :has_multiple_changes false
+                   :description          (str "BEFORE={:name \"Tips Created by Day\", :serialized true},AFTER="
+                                              "{:name \"Spots Created by Day\", :serialized true}.")})
+                 (mi/instance
+                  Revision
+                  {:is_reversion         false,
+                   :is_creation          false,
+                   :message              nil
+                   :user                 {:id (mt/user->id :rasta), :common_name "Rasta Toucan", :first_name "Rasta", :last_name "Toucan"},
+                   :diff                 {:o1 nil
+                                          :o2 {:name "Tips Created by Day", :serialized true}}
+                   :has_multiple_changes false
+                   :description          "modified this."})]
+                (->> (revision/revisions+details ::FakedCard card-id)
+                     (map #(dissoc % :timestamp :id :model_id))
+                     (map #(update % :description str)))))))))
 
 ;;; # REVERT
 
 (deftest revert-defer-to-revert-to-revision!-test
-  (testing "Check that revert defers to revert-to-revision!"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Tips Created by Day")
-      (let [[{revision-id :id}] (revision/revisions ::FakedCard card-id)]
-        (revision/revert! :entity ::FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id revision-id)
-        (is (= {:name "Tips Created by Day"}
-               @reverted-to))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Check that revert defers to revert-to-revision!"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Tips Created by Day")
+        (let [[{revision-id :id}] (revision/revisions ::FakedCard card-id)]
+          (revision/revert! :entity ::FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id revision-id)
+          (is (= {:name "Tips Created by Day"}
+                 @reverted-to)))))))
 
 (deftest revert-to-revision!-default-impl-test
-  (testing "Check default impl of revert-to-revision! just does mapply upd"
-    (t2.with-temp/with-temp [Card {card-id :id} {:name "Spots Created By Day"}]
-      (revision/push-revision! :entity Card, :id card-id, :user-id (mt/user->id :rasta), :object {:name "Tips Created by Day"})
-      (revision/push-revision! :entity Card, :id card-id, :user-id (mt/user->id :rasta), :object {:name "Spots Created by Day"})
-      (is (= "Spots Created By Day"
-             (:name (t2/select-one Card :id card-id))))
-      (let [[_ {old-revision-id :id}] (revision/revisions Card card-id)]
-        (revision/revert! :entity Card, :id card-id, :user-id (mt/user->id :rasta), :revision-id old-revision-id)
-        (is (= "Tips Created by Day"
-               (:name (t2/select-one Card :id card-id))))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Check default impl of revert-to-revision! just does mapply upd"
+      (t2.with-temp/with-temp [Card {card-id :id} {:name "Spots Created By Day"}]
+        (revision/push-revision! :entity Card, :id card-id, :user-id (mt/user->id :rasta), :object {:name "Tips Created by Day"})
+        (revision/push-revision! :entity Card, :id card-id, :user-id (mt/user->id :rasta), :object {:name "Spots Created by Day"})
+        (is (= "Spots Created By Day"
+               (:name (t2/select-one Card :id card-id))))
+        (let [[_ {old-revision-id :id}] (revision/revisions Card card-id)]
+          (revision/revert! :entity Card, :id card-id, :user-id (mt/user->id :rasta), :revision-id old-revision-id)
+          (is (= "Tips Created by Day"
+                 (:name (t2/select-one Card :id card-id)))))))))
 
 (deftest reverting-should-add-revision-test
-  (testing "Check that reverting to a previous revision adds an appropriate revision"
-    (t2.with-temp/with-temp [Card {card-id :id}]
-      (push-fake-revision! card-id, :name "Tips Created by Day")
-      (push-fake-revision! card-id, :name "Spots Created by Day")
-      (let [[_ {old-revision-id :id}] (revision/revisions ::FakedCard card-id)]
-        (revision/revert! :entity ::FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id old-revision-id)
-        (is (partial=
-             [(mi/instance
-               Revision
-               {:model        "FakedCard"
-                :user_id      (mt/user->id :rasta)
-                :object       {:name "Tips Created by Day", :serialized true}
-                :is_reversion true
-                :is_creation  false
-                :message      nil})
-              (mi/instance
-               Revision
-               {:model        "FakedCard",
-                :user_id      (mt/user->id :rasta)
-                :object       {:name "Spots Created by Day", :serialized true}
-                :is_reversion false
-                :is_creation  false
-                :message      nil})
-              (mi/instance
-               Revision
-               {:model        "FakedCard",
-                :user_id      (mt/user->id :rasta)
-                :object       {:name "Tips Created by Day", :serialized true}
-                :is_reversion false
-                :is_creation  false
-                :message      nil})]
-             (->> (revision/revisions ::FakedCard card-id)
-                  (map #(dissoc % :timestamp :id :model_id)))))))))
+  (mt/with-model-cleanup [:model/Revision]
+    (testing "Check that reverting to a previous revision adds an appropriate revision"
+      (t2.with-temp/with-temp [Card {card-id :id}]
+        (push-fake-revision! card-id, :name "Tips Created by Day")
+        (push-fake-revision! card-id, :name "Spots Created by Day")
+        (let [[_ {old-revision-id :id}] (revision/revisions ::FakedCard card-id)]
+          (revision/revert! :entity ::FakedCard, :id card-id, :user-id (mt/user->id :rasta), :revision-id old-revision-id)
+          (is (partial=
+               [(mi/instance
+                 Revision
+                 {:model        "FakedCard"
+                  :user_id      (mt/user->id :rasta)
+                  :object       {:name "Tips Created by Day", :serialized true}
+                  :is_reversion true
+                  :is_creation  false
+                  :message      nil})
+                (mi/instance
+                 Revision
+                 {:model        "FakedCard",
+                  :user_id      (mt/user->id :rasta)
+                  :object       {:name "Spots Created by Day", :serialized true}
+                  :is_reversion false
+                  :is_creation  false
+                  :message      nil})
+                (mi/instance
+                 Revision
+                 {:model        "FakedCard",
+                  :user_id      (mt/user->id :rasta)
+                  :object       {:name "Tips Created by Day", :serialized true}
+                  :is_reversion false
+                  :is_creation  false
+                  :message      nil})]
+               (->> (revision/revisions ::FakedCard card-id)
+                    (map #(dissoc % :timestamp :id :model_id))))))))))
 
 (deftest generic-models-revision-title+description-test
   (do-with-model-i18n-strs!
diff --git a/test/metabase/search/filter_test.clj b/test/metabase/search/filter_test.clj
new file mode 100644
index 0000000000000000000000000000000000000000..93b3d4d841e0ca4688c536c4a837e91422109a16
--- /dev/null
+++ b/test/metabase/search/filter_test.clj
@@ -0,0 +1,413 @@
+(ns ^:mb/once metabase.search.filter-test
+  (:require
+   [clojure.test :refer :all]
+   [metabase.public-settings.premium-features :as premium-features]
+   [metabase.public-settings.premium-features-test :as premium-features-test]
+   [metabase.search.config :as search.config]
+   [metabase.search.filter :as search.filter]
+   [metabase.test :as mt]))
+
+(def ^:private default-search-ctx
+  {:search-string       nil
+   :archived?           false
+   :models             search.config/all-models
+   :current-user-perms #{"/"}})
+
+(deftest ^:parallel ->applicable-models-test
+  (testing "without optional filters"
+    (testing "return :models as is"
+      (is (= search.config/all-models
+             (search.filter/search-context->applicable-models
+              default-search-ctx)))
+
+      (is (= #{}
+             (search.filter/search-context->applicable-models
+              (assoc default-search-ctx :models #{}))))
+
+      (is (= search.config/all-models
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:archived? true}))))))
+
+  (testing "optional filters will return intersection of support models and provided models\n"
+    (testing "created by"
+      (is (= #{"dashboard" "dataset" "action" "card"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:created-by #{1}}))))
+
+      (is (= #{"dashboard" "dataset"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:models #{"dashboard" "dataset" "table"}
+                      :created-by #{1}})))))
+
+    (testing "created at"
+      (is (= #{"dashboard" "table" "dataset" "collection" "database" "action" "card"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:created-at "past3days"}))))
+
+      (is (= #{"dashboard" "table" "dataset"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:models #{"dashboard" "dataset" "table"}
+                      :created-at "past3days"})))))
+
+    (testing "verified"
+      (is (= #{"dataset" "card"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:verified true}))))
+
+      (is (= #{"dataset"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:models   #{"dashboard" "dataset" "table"}
+                      :verified true})))))
+
+    (testing "last edited by"
+      (is (= #{"dashboard" "dataset" "card" "metric"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:last-edited-by #{1}}))))
+
+      (is (= #{"dashboard" "dataset"}
+             (search.filter/search-context->applicable-models
+              (merge default-search-ctx
+                     {:models         #{"dashboard" "dataset" "table"}
+                      :last-edited-by #{1}})))))
+
+   (testing "last edited at"
+     (is (= #{"dashboard" "dataset" "action" "metric" "card"}
+            (search.filter/search-context->applicable-models
+             (merge default-search-ctx
+                    {:last-edited-at "past3days"}))))
+
+     (is (= #{"dashboard" "dataset"}
+            (search.filter/search-context->applicable-models
+             (merge default-search-ctx
+                    {:models   #{"dashboard" "dataset" "table"}
+                     :last-edited-at "past3days"})))))
+
+   (testing "search native query"
+     (is (= #{"dataset" "action" "card"}
+            (search.filter/search-context->applicable-models
+             (merge default-search-ctx
+                    {:search-native-query true})))))))
+
+(deftest joined-with-table?-test
+  (are [expected args]
+       (= expected (apply #'search.filter/joined-with-table? args))
+
+       false
+       [{} :join :a]
+
+       true
+       [{:join [:a [:= :a.b :c.d]]} :join :a]
+
+       false
+       [{:join [:a [:= :a.b :c.d]]} :join :d]
+
+       ;; work with multiple join types
+       false
+       [{:join [:a [:= :a.b :c.d]]} :left-join :d]
+
+       ;; do the same with other join types too
+       true
+       [{:left-join [:a [:= :a.b :c.d]]} :left-join :a]
+
+       false
+       [{:left-join [:a [:= :a.b :c.d]]} :left-join :d]))
+
+
+(def ^:private base-search-query
+  {:select [:*]
+   :from   [:table]})
+
+(deftest ^:parallel build-archived-filter-test
+  (testing "archived filters"
+    (is (= [:= :card.archived false]
+           (:where (search.filter/build-filters
+                    base-search-query "card" default-search-ctx))))
+
+    (is (= [:and [:= :table.active true] [:= :table.visibility_type nil]]
+           (:where (search.filter/build-filters
+                    base-search-query "table"  default-search-ctx))))))
+
+(deftest ^:parallel build-filter-with-search-string-test
+  (testing "with search string"
+    (is (= [:and
+            [:or
+             [:like [:lower :card.name] "%a%"]
+             [:like [:lower :card.name] "%string%"]
+             [:like [:lower :card.description] "%a%"]
+             [:like [:lower :card.description] "%string%"]]
+            [:= :card.archived false]]
+           (:where (search.filter/build-filters
+                    base-search-query "card"
+                    (merge default-search-ctx {:search-string "a string"})))))))
+
+(deftest date-range-filter-clause-test
+  (mt/with-clock #t "2023-05-04T10:02:05Z[UTC]"
+    (are [created-at expected-where]
+         (= expected-where (#'search.filter/date-range-filter-clause :card.created_at created-at))
+         ;; absolute datetime
+         "Q1-2023"                                 [:and [:>= [:cast :card.created_at :date] #t "2023-01-01"]
+                                                    [:< [:cast :card.created_at :date]  #t "2023-04-01"]]
+         "2016-04-18~2016-04-23"                   [:and [:>= [:cast :card.created_at :date] #t "2016-04-18"]
+                                                    [:< [:cast :card.created_at :date]  #t "2016-04-24"]]
+         "2016-04-18"                              [:and [:>= [:cast :card.created_at :date] #t "2016-04-18"]
+                                                    [:< [:cast :card.created_at :date]  #t "2016-04-19"]]
+         "2023-05-04~"                             [:> [:cast :card.created_at :date]  #t "2023-05-04"]
+         "~2023-05-04"                             [:< [:cast :card.created_at :date]  #t "2023-05-05"]
+         "2016-04-18T10:30:00~2016-04-23T11:30:00" [:and [:>= :card.created_at #t "2016-04-18T10:30"]
+                                                    [:< :card.created_at #t "2016-04-23T11:31:00"]]
+         "2016-04-23T10:00:00"                     [:and [:>= :card.created_at #t "2016-04-23T10:00"]
+                                                    [:< :card.created_at  #t "2016-04-23T10:01"]]
+         "2016-04-18T10:30:00~"                    [:> :card.created_at #t "2016-04-18T10:30"]
+         "~2016-04-18T10:30:00"                    [:< :card.created_at #t "2016-04-18T10:31"]
+         ;; relative datetime
+         "past3days"                               [:and [:>= [:cast :card.created_at :date] #t "2023-05-01"]
+                                                    [:< [:cast :card.created_at :date]  #t "2023-05-04"]]
+         "past3days~"                              [:and [:>= [:cast :card.created_at :date] #t "2023-05-01"]
+                                                    [:< [:cast :card.created_at :date] #t "2023-05-05"]]
+         "past3hours~"                             [:and [:>= :card.created_at #t "2023-05-04T07:00"]
+                                                    [:< :card.created_at #t "2023-05-04T11:00"]]
+         "next3days"                               [:and [:>= [:cast :card.created_at :date] #t "2023-05-05"]
+                                                    [:< [:cast :card.created_at :date]  #t "2023-05-08"]]
+         "thisminute"                              [:and [:>= :card.created_at #t "2023-05-04T10:02"]
+                                                    [:< :card.created_at #t "2023-05-04T10:03"]]
+         "lasthour"                                [:and [:>= :card.created_at #t "2023-05-04T09:00"]
+                                                    [:< :card.created_at #t "2023-05-04T10:00"]]
+         "past1months-from-36months"               [:and [:>= [:cast :card.created_at :date] #t "2020-04-01"]
+                                                    [:< [:cast :card.created_at :date]  #t "2020-05-01"]]
+         "today"                                   [:and [:>= [:cast :card.created_at :date] #t "2023-05-04"]
+                                                    [:< [:cast :card.created_at :date] #t "2023-05-05"]]
+         "yesterday"                               [:and [:>= [:cast :card.created_at :date] #t "2023-05-03"]
+                                                    [:< [:cast :card.created_at :date] #t "2023-05-04"]])))
+
+;; both created at and last-edited-at use [[search.filter/date-range-filter-clause]]
+;; to generate the filter clause so for the full test cases, check [[date-range-filter-clause-test]]
+;; these 2 tests are for checking the shape of the query
+(deftest ^:parallel created-at-filter-test
+  (testing "created-at filter"
+    (is (= {:select [:*]
+            :from   [:table]
+            :where  [:and
+                     [:= :card.archived false]
+                     [:>= [:cast :card.created_at :date] #t "2016-04-18"]
+                     [:< [:cast :card.created_at :date]  #t "2016-04-24"]]}
+           (search.filter/build-filters
+            base-search-query "card"
+            (merge default-search-ctx {:created-at "2016-04-18~2016-04-23"}))))))
+
+(deftest ^:parallel last-edited-at-filter-test
+  (testing "last edited at filter"
+    (is (= {:select [:*]
+            :from   [:table]
+            :join   [:revision [:= :revision.model_id :card.id]]
+            :where  [:and
+                     [:= :card.archived false]
+                     [:= :revision.most_recent true]
+                     [:= :revision.model "Card"]
+                     [:>= [:cast :revision.timestamp :date] #t "2016-04-18"]
+                     [:< [:cast :revision.timestamp :date] #t "2016-04-24"]]}
+           (search.filter/build-filters
+            base-search-query "dataset"
+            (merge default-search-ctx {:last-edited-at "2016-04-18~2016-04-23"}))))
+
+   (testing "do not join twice if has both last-edited-at and last-edited-by"
+     (is (= {:select [:*]
+             :from   [:table]
+             :join   [:revision [:= :revision.model_id :card.id]]
+             :where  [:and
+                      [:= :card.archived false]
+                      [:= :revision.most_recent true]
+                      [:= :revision.model "Card"]
+                      [:>= [:cast :revision.timestamp :date] #t "2016-04-18"]
+                      [:< [:cast :revision.timestamp :date] #t "2016-04-24"]
+                      [:= :revision.user_id 1]]}
+            (search.filter/build-filters
+             base-search-query "dataset"
+             (merge default-search-ctx {:last-edited-at "2016-04-18~2016-04-23"
+                                        :last-edited-by #{1}})))))
+
+   (testing "for actiion"
+     (is (= {:select [:*]
+             :from   [:table]
+             :where  [:and [:= :action.archived false]
+                      [:>= [:cast :action.updated_at :date] #t "2016-04-18"]
+                      [:< [:cast :action.updated_at :date] #t "2016-04-24"]]}
+            (search.filter/build-filters
+             base-search-query "action"
+             (merge default-search-ctx {:last-edited-at "2016-04-18~2016-04-23"})))))))
+
+(deftest ^:parallel build-created-by-filter-test
+  (testing "created-by filter"
+    (is (= [:and [:= :card.archived false] [:= :card.creator_id 1]]
+           (:where (search.filter/build-filters
+                    base-search-query "card"
+                    (merge default-search-ctx
+                           {:created-by #{1}})))))
+    (is (= [:and [:= :card.archived false] [:in :card.creator_id #{1 2}]]
+           (:where (search.filter/build-filters
+                    base-search-query "card"
+                    (merge default-search-ctx
+                           {:created-by #{1 2}})))))))
+
+(deftest ^:parallel build-last-edited-by-filter-test
+  (testing "last edited by filter"
+    (is (= {:select [:*]
+            :from   [:table]
+            :where  [:and
+                     [:= :card.archived false]
+                     [:= :revision.most_recent true]
+                     [:= :revision.model "Card"]
+                     [:= :revision.user_id 1]]
+            :join   [:revision [:= :revision.model_id :card.id]]}
+           (search.filter/build-filters
+            base-search-query "dataset"
+            (merge default-search-ctx
+                   {:last-edited-by #{1}})))))
+
+  (testing "last edited by filter"
+    (is (= {:select [:*]
+            :from   [:table]
+            :where  [:and
+                     [:= :card.archived false]
+                     [:= :revision.most_recent true]
+                     [:= :revision.model "Card"]
+                     [:in :revision.user_id #{1 2}]]
+            :join   [:revision [:= :revision.model_id :card.id]]}
+           (search.filter/build-filters
+            base-search-query "dataset"
+            (merge default-search-ctx
+                   {:last-edited-by #{1 2}}))))))
+
+(deftest build-verified-filter-test
+  (testing "verified filter"
+    (premium-features-test/with-premium-features #{:content-verification}
+      (testing "for cards"
+        (is (= (merge
+                base-search-query
+                {:where  [:and
+                          [:= :card.archived false]
+                          [:= :moderation_review.status "verified"]
+                          [:= :moderation_review.moderated_item_type "card"]
+                          [:= :moderation_review.most_recent true]]
+                 :join   [:moderation_review [:= :moderation_review.moderated_item_id :card.id]]})
+               (search.filter/build-filters
+                base-search-query "card"
+                (merge default-search-ctx {:verified true})))))
+
+      (testing "for models"
+        (is (= (merge
+                base-search-query
+                {:where  [:and
+                          [:= :card.archived false]
+                          [:= :moderation_review.status "verified"]
+                          [:= :moderation_review.moderated_item_type "card"]
+                          [:= :moderation_review.most_recent true]]
+                 :join   [:moderation_review [:= :moderation_review.moderated_item_id :card.id]]})
+               (search.filter/build-filters
+                base-search-query "dataset"
+                (merge default-search-ctx {:verified true}))))))
+
+    (premium-features-test/with-premium-features #{}
+      (testing "for cards without ee features"
+        (is (= (merge
+                base-search-query
+                {:where  [:and
+                          [:= :card.archived false]
+                          [:inline [:= 0 1]]]})
+               (search.filter/build-filters
+                base-search-query "card"
+                (merge default-search-ctx {:verified true})))))
+
+      (testing "for models without ee features"
+        (is (= (merge
+                base-search-query
+                {:where  [:and
+                          [:= :card.archived false]
+                          [:inline [:= 0 1]]]})
+               (search.filter/build-filters
+                base-search-query "dataset"
+                (merge default-search-ctx {:verified true}))))))))
+
+(deftest ^:parallel build-filter-throw-error-for-unsuported-filters-test
+  (testing "throw error for filtering with unsupport models"
+    (is (thrown-with-msg?
+         clojure.lang.ExceptionInfo
+         #":created-by filter for database is not supported"
+         (search.filter/build-filters
+          base-search-query
+          "database"
+          (merge default-search-ctx
+                 {:created-by #{1}}))))))
+
+(deftest build-filters-indexed-entity-test
+  (testing "users that are not sandboxed or impersonated can search for indexed entity"
+    (with-redefs [premium-features/sandboxed-or-impersonated-user? (constantly false)]
+      (is (= [:and
+              [:or [:like [:lower :model-index-value.name] "%foo%"]]
+              [:inline [:= 1 1]]]
+             (:where (search.filter/build-filters
+                      base-search-query
+                      "indexed-entity"
+                      (merge default-search-ctx {:search-string "foo"})))))))
+
+  (testing "otherwise search result is empty"
+    (with-redefs [premium-features/sandboxed-or-impersonated-user? (constantly true)]
+      (is (= [:and
+              [:or [:= 0 1]]
+              [:inline [:= 1 1]]]
+             (:where (search.filter/build-filters
+                      base-search-query
+                      "indexed-entity"
+                      (merge default-search-ctx {:search-string "foo"}))))))))
+
+(deftest build-filters-search-native-query
+  (doseq [model ["dataset" "card"]]
+    (testing model
+      (testing "do not search for native query by default"
+        (is (= [:and
+                [:or [:like [:lower :card.name] "%foo%"] [:like [:lower :card.description] "%foo%"]]
+                [:= :card.archived false]]
+               (:where (search.filter/build-filters
+                        base-search-query
+                        model
+                        (merge default-search-ctx {:search-string "foo"}))))))
+
+      (testing "search in both name, description and dataset_query if is enabled"
+        (is (= [:and [:or
+                      [:like [:lower :card.name] "%foo%"]
+                      [:and [:= :card.query_type "native"] [:like [:lower :card.dataset_query] "%foo%"]]
+                      [:like [:lower :card.description] "%foo%"]]
+                [:= :card.archived false]]
+               (:where (search.filter/build-filters
+                        base-search-query
+                        model
+                        (merge default-search-ctx {:search-string "foo" :search-native-query true})))))))
+
+    (testing "action"
+      (testing "do not search for native query by default"
+        (is (= [:and
+                [:or [:like [:lower :action.name] "%foo%"] [:like [:lower :action.description] "%foo%"]]
+                [:= :action.archived false]]
+               (:where (search.filter/build-filters
+                        base-search-query
+                        "action"
+                        (merge default-search-ctx {:search-string "foo"}))))))
+
+      (testing "search in both name, description and dataset_query if is enabled"
+        (is (= [:and
+                [:or
+                 [:like [:lower :action.name] "%foo%"]
+                 [:like [:lower :query_action.dataset_query] "%foo%"]
+                 [:like [:lower :action.description] "%foo%"]]
+                [:= :action.archived false]]
+               (:where (search.filter/build-filters
+                        base-search-query
+                        "action"
+                        (merge default-search-ctx {:search-string "foo" :search-native-query true})))))))))
diff --git a/test/metabase/search/scoring_test.clj b/test/metabase/search/scoring_test.clj
index 879f43a67d1b05f4d5a02e943ea48c278583a13a..aaf168aef4d9591f65c6f5e62dd5961ba1d30353 100644
--- a/test/metabase/search/scoring_test.clj
+++ b/test/metabase/search/scoring_test.clj
@@ -1,8 +1,9 @@
 (ns metabase.search.scoring-test
   (:require
+   [cheshire.core :as json]
    [clojure.test :refer :all]
    [java-time.api :as t]
-   [metabase.search.config :as search-config]
+   [metabase.search.config :as search.config]
    [metabase.search.scoring :as scoring]))
 
 (defn- result-row
@@ -136,7 +137,7 @@
         (is (= (map :result items)
                (scoring/top-results items large xf)))))
     (testing "a full queue only saves the top items"
-      (let [sorted-items (->> (+ small search-config/max-filtered-results)
+      (let [sorted-items (->> (+ small search.config/max-filtered-results)
                               range
                               reverse ;; descending order
                               (map (fn [i]
@@ -217,7 +218,7 @@
                   reverse
                   (map :id)))))
     (testing "it treats stale items as being equally old"
-      (let [stale search-config/stale-time-in-days]
+      (let [stale search.config/stale-time-in-days]
         (is (= [1 2 3 4]
                (->> [(item 1 (days-ago (+ stale 1)))
                      (item 2 (days-ago (+ stale 50)))
@@ -272,7 +273,21 @@
                      {:weight 100 :score 0 :name "Some other score type"}])]
       (is (= 0 (:score (scoring/score-and-result "" {:name "racing yo" :model "card"})))))))
 
-(deftest force-weight-test
+(deftest ^:parallel serialize-test
+  (testing "It normalizes dataset queries from strings"
+    (let [query  {:type     :query
+                  :query    {:source-query {:source-table 1}}
+                  :database 1}
+          result {:name          "card"
+                  :model         "card"
+                  :dataset_query (json/generate-string query)}]
+      (is (= query (-> result (#'scoring/serialize {} {}) :dataset_query)))))
+  (testing "Doesn't error on other models without a query"
+    (is (nil? (-> {:name "dash" :model "dashboard"}
+                  (#'scoring/serialize {} {})
+                  :dataset_query)))))
+
+(deftest ^:parallel force-weight-test
   (is (= [{:weight 10}]
          (scoring/force-weight [{:weight 1}] 10)))
 
diff --git a/test/metabase/search/util_test.clj b/test/metabase/search/util_test.clj
index 116d1cfadf625cdd7d5b9a3426a83fce7b8c4e8f..f9f9d822b7a1af49e63fedfc15086f6734e49c80 100644
--- a/test/metabase/search/util_test.clj
+++ b/test/metabase/search/util_test.clj
@@ -1,23 +1,23 @@
 (ns metabase.search.util-test
   (:require
    [clojure.test :refer :all]
-   [metabase.search.util :as search-util]))
+   [metabase.search.util :as search.util]))
 
 (deftest ^:parallel tokenize-test
   (testing "basic tokenization"
     (is (= ["Rasta" "the" "Toucan's" "search"]
-           (search-util/tokenize "Rasta the Toucan's search")))
+           (search.util/tokenize "Rasta the Toucan's search")))
     (is (= ["Rasta" "the" "Toucan"]
-           (search-util/tokenize "                Rasta\tthe    \tToucan     ")))
+           (search.util/tokenize "                Rasta\tthe    \tToucan     ")))
     (is (= []
-           (search-util/tokenize " \t\n\t ")))
+           (search.util/tokenize " \t\n\t ")))
     (is (= []
-           (search-util/tokenize "")))
+           (search.util/tokenize "")))
     (is (thrown-with-msg? Exception #"should be a string"
-                          (search-util/tokenize nil)))))
+                          (search.util/tokenize nil)))))
 
 (deftest ^:parallel test-largest-common-subseq-length
-  (let [subseq-length (partial search-util/largest-common-subseq-length =)]
+  (let [subseq-length (partial search.util/largest-common-subseq-length =)]
     (testing "greedy choice can't be taken"
       (is (= 3
              (subseq-length ["garden" "path" "this" "is" "not" "a" "garden" "path"]
diff --git a/test/metabase/test.clj b/test/metabase/test.clj
index b0635ddd3de8ad297265f8485e22cd40c3d9cf3c..e2e48d825fb66808ca56d8d26fdb0b34e887e1ed 100644
--- a/test/metabase/test.clj
+++ b/test/metabase/test.clj
@@ -244,7 +244,8 @@
   with-temp-vals-in-db
   with-temporary-setting-values
   with-temporary-raw-setting-values
-  with-user-in-groups]
+  with-user-in-groups
+  with-verified-cards]
 
  [tu.async
   wait-for-result
diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj
index 6beb24a169220afc754449932d5ff4529dea335e..808f09ded64dc3decd26601631d2bb4e97700710 100644
--- a/test/metabase/test/util.clj
+++ b/test/metabase/test/util.clj
@@ -28,6 +28,7 @@
             User]]
    [metabase.models.collection :as collection]
    [metabase.models.interface :as mi]
+   [metabase.models.moderation-review :as moderation-review]
    [metabase.models.permissions :as perms]
    [metabase.models.permissions-group :as perms-group]
    [metabase.models.setting :as setting]
@@ -118,6 +119,9 @@
    :model/Collection
    (fn [_] (default-created-at-timestamped {:name (tu.random/random-name)}))
 
+   :model/Action
+   (fn [_] {:creator_id (rasta-id)})
+
    :model/Dashboard
    (fn [_] (default-timestamped
              {:creator_id (rasta-id)
@@ -556,6 +560,8 @@
        ~@body)))
 
 
+
+
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                   SCHEDULER                                                    |
 ;;; +----------------------------------------------------------------------------------------------------------------+
@@ -704,6 +710,49 @@
           (testing "Shouldn't delete other Cards"
             (is (pos? (t2/count Card)))))))))
 
+(defn do-with-verified-cards
+  "Impl for [[with-verified-cards]]."
+  [card-or-ids thunk]
+  (with-model-cleanup [:model/ModerationReview]
+    (doseq [card-or-id card-or-ids]
+      (doseq [status ["verified" nil "verified"]]
+        ;; create multiple moderation review for a card, but the end result is it's still verified
+        (moderation-review/create-review!
+         {:moderated_item_id   (u/the-id card-or-id)
+          :moderated_item_type "card"
+          :moderator_id        ((requiring-resolve 'metabase.test.data.users/user->id) :rasta)
+          :status              status})))
+    (thunk)))
+
+(defmacro with-verified-cards
+  "Execute the body with all `card-or-ids` verified."
+  [card-or-ids & body]
+  `(do-with-verified-cards ~card-or-ids (fn [] ~@body)))
+
+(deftest with-verified-cards-test
+  (t2.with-temp/with-temp
+    [:model/Card {card-id :id} {}]
+    (with-verified-cards [card-id]
+      (is (=? #{{:moderated_item_id   card-id
+                 :moderated_item_type :card
+                 :most_recent         true
+                 :status              "verified"}
+                {:moderated_item_id   card-id
+                 :moderated_item_type :card
+                 :most_recent         false
+                 :status              nil}
+                {:moderated_item_id   card-id
+                 :moderated_item_type :card
+                 :most_recent         false
+                 :status              "verified"}}
+              (t2/select-fn-set #(select-keys % [:moderated_item_id :moderated_item_type :most_recent :status])
+                                :model/ModerationReview
+                                :moderated_item_id card-id
+                                :moderated_item_type "card"))))
+    (testing "everything is cleaned up after the macro"
+      (is (= 0 (t2/count :model/ModerationReview
+                         :moderated_item_id card-id
+                         :moderated_item_type "card"))))))
 
 ;; TODO - not 100% sure I understand
 (defn call-with-paused-query