Repository: metabase/metabase
Pull request #57677: Fix parameter types!
Author: login: ranquild
Labels: name: no-backport
description: Do not backport this PR to any branch
name: .Team/Querying
name: PR-Env
description: See https://www.notion.so/metabase/Ephemeral-PR-Environments-c74a3710c87a460dbf4fc0007a001458
Created: 2025-05-07T01:42:24Z | Merged: 2025-05-08T01:16:33Z

### Location parameter fixes

We're super lucky that there was a long-standing bug in `location?` check - it checked for the `type/Address` and not `type/Location`. It means that effectively all `location` parameters are mapped to string columns! This PR makes this an explicit constraint that we can rely on - when mapping columns and when parsing parameter values.

### X-ray parameters types

X-rays used to generate completely broken parameter types. This PR fixes the issue and uses the types that the app actually supports.

### Broken parameters types after this PR is merged

- `string/<operator>` parameters can be mapped to string columns and boolean **fields** only. Before they could be mapped to numeric columns as well if they had the `type/Category` type set.
- `category`, `location/city`, `location/state`, `location/zip_code`, `location/country` are no longer used in x-rays but can exist in previously saved x-ray dashboards.
- `id` parameters can be used with anything, and we cannot currently parse their values correctly.

The next steps:
1. Add proper `boolean/=` parameters https://github.com/metabase/metabase/pull/57435
2. Disallow new mappings between `string/<operator>` parameters and boolean columns to be created
3. Migrate invalid usages of `string/<operator>` to `number/<operator>` and `boolean/=`.
4. Migrate `category`, `location/city`, `location/state`, `location/zip_code`, `location/country` to `string/=` or `number/=` parameters.
5. Figure out what to do with `id`. There are a couple of options - either drop it completely, or introduce something like `id/string` and `id/number`, with `id/number` being the default.

How to verify:
- X-ray `People` table and make sure each parameter works


Diff:
diff --git a/frontend/src/metabase-lib/v1/metadata/Field.ts b/frontend/src/metabase-lib/v1/metadata/Field.ts
index f2bd23afb6cb..2544f29386ad 100644
--- a/frontend/src/metabase-lib/v1/metadata/Field.ts
+++ b/frontend/src/metabase-lib/v1/metadata/Field.ts
@@ -11,6 +11,7 @@ import {
 } from "metabase-lib/v1/queries/utils/field";
 import { TYPE } from "metabase-lib/v1/types/constants";
 import {
+  isAddress,
   isBoolean,
   isCoordinate,
   isCurrency,
@@ -18,7 +19,6 @@ import {
   isDateWithoutTime,
   isDimension,
   isFK,
-  isLocation,
   isMetric,
   isNumber,
   isNumeric,
@@ -197,8 +197,8 @@ export default class Field extends Base {
     return isCoordinate(this);
   }
 
-  isLocation() {
-    return isLocation(this);
+  isAddress() {
+    return isAddress(this);
   }
 
   isSummable() {
diff --git a/frontend/src/metabase-lib/v1/parameters/utils/click-behavior.unit.spec.ts b/frontend/src/metabase-lib/v1/parameters/utils/click-behavior.unit.spec.ts
index bbc651e106c7..5aad290d1db1 100644
--- a/frontend/src/metabase-lib/v1/parameters/utils/click-behavior.unit.spec.ts
+++ b/frontend/src/metabase-lib/v1/parameters/utils/click-behavior.unit.spec.ts
@@ -500,7 +500,7 @@ describe("metabase/lib/click-behavior", () => {
   });
 
   describe("formatSourceForTarget", () => {
-    it("should not change text parameters", () => {
+    it("should validate date parameter values", () => {
       const source = {
         type: "column" as const,
         id: "SOME_STRING",
@@ -529,7 +529,7 @@ describe("metabase/lib/click-behavior", () => {
         extraData,
         clickBehavior,
       });
-      expect(value).toEqual("foo");
+      expect(value).toEqual(null);
     });
 
     it("should format date/all-options parameters based on unit", () => {
diff --git a/frontend/src/metabase-lib/v1/parameters/utils/filters.ts b/frontend/src/metabase-lib/v1/parameters/utils/filters.ts
index 05a326ef4294..24dc9564d144 100644
--- a/frontend/src/metabase-lib/v1/parameters/utils/filters.ts
+++ b/frontend/src/metabase-lib/v1/parameters/utils/filters.ts
@@ -20,7 +20,7 @@ type ColumnInfo = {
   isBoolean: boolean;
   isTemporal: boolean;
   isID: boolean;
-  isLocation: boolean;
+  isAddress: boolean;
   isTemporalBucketable: boolean;
   hasFieldValues: FieldValuesType | undefined;
 };
@@ -33,7 +33,7 @@ function isParameterCompatibleWithColumn(
     isBoolean,
     isTemporal,
     isID,
-    isLocation,
+    isAddress,
     isTemporalBucketable,
     hasFieldValues,
   }: ColumnInfo,
@@ -47,12 +47,12 @@ function isParameterCompatibleWithColumn(
     case "category":
       return hasFieldValues === "list";
     case "location":
-      return isLocation;
+      return isString && isAddress;
     case "number":
-      return isNumeric && !isID && !isLocation;
+      return isNumeric && !isID;
     case "string":
       return (
-        (isString || (isBoolean && hasFieldValues === "list")) && !isLocation
+        (isString || (isBoolean && hasFieldValues === "list")) && !isAddress
       );
     case "temporal-unit":
       return isTemporalBucketable;
@@ -71,7 +71,7 @@ export function fieldFilterForParameter(
       isBoolean: field.isBoolean(),
       isTemporal: field.isDate(),
       isID: field.isID(),
-      isLocation: field.isLocation(),
+      isAddress: field.isAddress(),
       isTemporalBucketable: false,
       hasFieldValues: field.has_field_values,
     });
@@ -89,7 +89,7 @@ export function columnFilterForParameter(
       isBoolean: Lib.isBoolean(column),
       isTemporal: Lib.isTemporal(column),
       isID: Lib.isID(column),
-      isLocation: Lib.isLocation(column),
+      isAddress: Lib.isAddress(column),
       isTemporalBucketable: Lib.isTemporalBucketable(query, stageIndex, column),
       hasFieldValues: Lib.fieldValuesSearchInfo(query, column).hasFieldValues,
     });
diff --git a/frontend/src/metabase-lib/v1/parameters/utils/filters.unit.spec.ts b/frontend/src/metabase-lib/v1/parameters/utils/filters.unit.spec.ts
index de98c29cd465..b11209c082ec 100644
--- a/frontend/src/metabase-lib/v1/parameters/utils/filters.unit.spec.ts
+++ b/frontend/src/metabase-lib/v1/parameters/utils/filters.unit.spec.ts
@@ -21,7 +21,7 @@ describe("parameters/utils/field-filters", () => {
       isNumeric: () => false,
       isString: () => false,
       isBoolean: () => false,
-      isLocation: () => false,
+      isAddress: () => false,
       isCoordinate: () => false,
     });
 
@@ -57,7 +57,8 @@ describe("parameters/utils/field-filters", () => {
           type: "location",
           field: () => ({
             ...field,
-            isLocation: () => true,
+            isString: () => true,
+            isAddress: () => true,
             isCity: () => true,
           }),
         },
@@ -134,7 +135,7 @@ describe("parameters/utils/field-filters", () => {
       const locationDimension = createMockDimension({
         field: () => ({
           ...field,
-          isLocation: () => true,
+          isAddress: () => true,
         }),
       });
 
diff --git a/frontend/src/metabase-lib/v1/parameters/utils/parameter-parsing.ts b/frontend/src/metabase-lib/v1/parameters/utils/parameter-parsing.ts
index f0c78765db88..a7f09813039c 100644
--- a/frontend/src/metabase-lib/v1/parameters/utils/parameter-parsing.ts
+++ b/frontend/src/metabase-lib/v1/parameters/utils/parameter-parsing.ts
@@ -1,9 +1,11 @@
 import type { Query } from "history";
 
 import {
-  deserializeBooleanParameterValue,
-  deserializeStringParameterValue,
+  normalizeBooleanParameterValue,
+  normalizeDateParameterValue,
   normalizeNumberParameterValue,
+  normalizeStringParameterValue,
+  normalizeTemporalUnitParameterValue,
 } from "metabase/querying/parameters/utils/parsing";
 import * as Lib from "metabase-lib";
 import type Field from "metabase-lib/v1/metadata/Field";
@@ -56,8 +58,19 @@ export function parseParameterValue(value: any, parameter: Parameter) {
     return parseParameterValueForFields(coercedValue, fields);
   }
 
-  if (type === "number") {
-    return parseParameterValueForNumber(coercedValue);
+  // Note:
+  // - "string" parameters can be mapped to numeric and boolean columns
+  // - "category" and "id" parameters can be mapped to anything
+  // We cannot properly deserialize their values by checking the parameter type only
+  switch (type) {
+    case "number":
+      return parseParameterValueForNumber(coercedValue);
+    case "location":
+      return normalizeStringParameterValue(coercedValue);
+    case "date":
+      return normalizeDateParameterValue(coercedValue);
+    case "temporal-unit":
+      return normalizeTemporalUnitParameterValue(coercedValue);
   }
 
   return coercedValue;
@@ -96,11 +109,11 @@ function parseParameterValueForFields(
   }
 
   if (fields.every((f) => f.isBoolean())) {
-    return deserializeBooleanParameterValue(value);
+    return normalizeBooleanParameterValue(value);
   }
 
   if (fields.every((f) => f.isString() || f.isStringLike())) {
-    return deserializeStringParameterValue(value);
+    return normalizeStringParameterValue(value);
   }
 
   return value;
diff --git a/frontend/src/metabase-lib/v1/parameters/utils/template-tag-options.unit.spec.js b/frontend/src/metabase-lib/v1/parameters/utils/template-tag-options.unit.spec.js
index 6de7562540f7..b549acf22ccd 100644
--- a/frontend/src/metabase-lib/v1/parameters/utils/template-tag-options.unit.spec.js
+++ b/frontend/src/metabase-lib/v1/parameters/utils/template-tag-options.unit.spec.js
@@ -51,7 +51,7 @@ describe("parameters/utils/template-tag-options", () => {
       isNumeric: () => false,
       isString: () => false,
       isBoolean: () => false,
-      isLocation: () => false,
+      isAddress: () => false,
     };
 
     it("should return relevantly typed options for date field", () => {
@@ -78,9 +78,13 @@ describe("parameters/utils/template-tag-options", () => {
       ).toBe(true);
     });
 
-    it("should return string options for a location field", () => {
-      const locationField = { ...field, isLocation: () => true };
-      const availableOptions = getParameterOptionsForField(locationField);
+    it("should return string options for an address field", () => {
+      const addressField = {
+        ...field,
+        isString: () => true,
+        isAddress: () => true,
+      };
+      const availableOptions = getParameterOptionsForField(addressField);
       expect(
         availableOptions.length > 0 &&
           availableOptions.every((option) => option.type.startsWith("string")),
diff --git a/frontend/src/metabase-lib/v1/types/utils/isa.js b/frontend/src/metabase-lib/v1/types/utils/isa.js
index f58d75c4085e..07bacdafcefa 100644
--- a/frontend/src/metabase-lib/v1/types/utils/isa.js
+++ b/frontend/src/metabase-lib/v1/types/utils/isa.js
@@ -106,7 +106,6 @@ export const isBoolean = isFieldType.bind(null, BOOLEAN);
 export const isString = isFieldType.bind(null, STRING);
 export const isStringLike = isFieldType.bind(null, STRING_LIKE);
 export const isSummable = isFieldType.bind(null, SUMMABLE);
-export const isLocation = isFieldType.bind(null, LOCATION);
 
 const hasNonMetricName = (col) => {
   const name = col.name.toLowerCase();
@@ -121,6 +120,8 @@ export const isFK = (field) => field && isTypeFK(field.semantic_type);
 export const isPK = (field) => field && isTypePK(field.semantic_type);
 export const isEntityName = (field) =>
   field && isa(field.semantic_type, TYPE.Name);
+export const isAddress = (field) =>
+  field && isa(field.semantic_type, TYPE.Address);
 
 export const isAny = (col) => true;
 
diff --git a/frontend/src/metabase/querying/parameters/utils/parsing.ts b/frontend/src/metabase/querying/parameters/utils/parsing.ts
index 78073bc1534b..d2eb6e0eaadb 100644
--- a/frontend/src/metabase/querying/parameters/utils/parsing.ts
+++ b/frontend/src/metabase/querying/parameters/utils/parsing.ts
@@ -3,7 +3,8 @@ import dayjs from "dayjs";
 import { type NumberValue, parseNumber } from "metabase/lib/number";
 import type { DateFilterValue } from "metabase/querying/filters/types";
 import { isDatePickerTruncationUnit } from "metabase/querying/filters/utils/dates";
-import type { ParameterValueOrArray } from "metabase-types/api";
+import * as Lib from "metabase-lib";
+import type { ParameterValueOrArray, TemporalUnit } from "metabase-types/api";
 
 function normalizeArray(value: ParameterValueOrArray | null | undefined) {
   if (value == null) {
@@ -24,6 +25,12 @@ export function deserializeStringParameterValue(
   }, []);
 }
 
+export function normalizeStringParameterValue(
+  value: ParameterValueOrArray | null | undefined,
+): string[] {
+  return deserializeStringParameterValue(value);
+}
+
 export function serializeNumberParameterValue(
   value: NumberValue[],
 ): ParameterValueOrArray {
@@ -72,6 +79,12 @@ export function deserializeBooleanParameterValue(
   }, []);
 }
 
+export function normalizeBooleanParameterValue(
+  value: ParameterValueOrArray | null | undefined,
+): boolean[] {
+  return deserializeBooleanParameterValue(value);
+}
+
 function serializeDate(date: Date, hasTime: boolean) {
   return hasTime
     ? dayjs(date).format("YYYY-MM-DDTHH:mm:ss")
@@ -544,3 +557,24 @@ export function deserializeDateParameterValue(
 
   return null;
 }
+
+export function normalizeDateParameterValue(
+  value: ParameterValueOrArray | null | undefined,
+): string | null {
+  const dateValue = deserializeDateParameterValue(value);
+  return dateValue != null ? serializeDateParameterValue(dateValue) : null;
+}
+
+export function deserializeTemporalUnitParameterValue(
+  value: ParameterValueOrArray | null | undefined,
+): TemporalUnit | null {
+  const availableUnits = Lib.availableTemporalUnits();
+  const matchedUnit = availableUnits.find((unit) => unit === value);
+  return matchedUnit != null ? matchedUnit : null;
+}
+
+export function normalizeTemporalUnitParameterValue(
+  value: ParameterValueOrArray | null | undefined,
+): TemporalUnit | null {
+  return deserializeTemporalUnitParameterValue(value);
+}
diff --git a/frontend/src/metabase/querying/parameters/utils/parsing.unit.spec.ts b/frontend/src/metabase/querying/parameters/utils/parsing.unit.spec.ts
index 335afef23d62..186cf60541ef 100644
--- a/frontend/src/metabase/querying/parameters/utils/parsing.unit.spec.ts
+++ b/frontend/src/metabase/querying/parameters/utils/parsing.unit.spec.ts
@@ -6,6 +6,7 @@ import {
   deserializeDateParameterValue,
   deserializeNumberParameterValue,
   deserializeStringParameterValue,
+  deserializeTemporalUnitParameterValue,
   normalizeNumberParameterValue,
   serializeDateParameterValue,
   serializeNumberParameterValue,
@@ -402,3 +403,20 @@ describe("date parameters", () => {
     expect(deserializeDateParameterValue(value)).toBeNull();
   });
 });
+
+describe("temporal unit parameters", () => {
+  it.each([
+    { value: "day", expectedValue: "day" },
+    { value: "year", expectedValue: "year" },
+    { value: "day-of-year", expectedValue: "day-of-year" },
+  ])("should deserialize $value", ({ value, expectedValue }) => {
+    expect(deserializeTemporalUnitParameterValue(value)).toEqual(expectedValue);
+  });
+
+  it.each([null, undefined, "", [""], ["abc"], ["year"], 1, NaN, [NaN]])(
+    "should ignore invalid value %s",
+    (value) => {
+      expect(deserializeTemporalUnitParameterValue(value)).toEqual(null);
+    },
+  );
+});
diff --git a/src/metabase/lib/filter/operator.cljc b/src/metabase/lib/filter/operator.cljc
index 7890d03cd73d..4620cab8f4d5 100644
--- a/src/metabase/lib/filter/operator.cljc
+++ b/src/metabase/lib/filter/operator.cljc
@@ -102,8 +102,7 @@
    There are slight differences between names and ordering for the different base types."
   [column :- ::lib.schema.metadata/column]
   ;; The order of these clauses is important since we want to match the most relevant type
-  ;; the order is different than `lib.types.isa/field-type` as filters need to operate
-  ;; on the effective-type rather than the semantic-type, eg boolean and number cannot become
+  ;; filters need to operate on the effective-type rather than the semantic-type, eg boolean and number cannot become
   ;; string if semantic type is type/Category
   (condp lib.types.isa/field-type? column
     :metabase.lib.types.constants/temporal    temporal-operators
diff --git a/src/metabase/lib/types/isa.cljc b/src/metabase/lib/types/isa.cljc
index cc3bd94f7471..b9688e317fef 100644
--- a/src/metabase/lib/types/isa.cljc
+++ b/src/metabase/lib/types/isa.cljc
@@ -2,7 +2,6 @@
   "Ported from frontend/src/metabase-lib/types/utils/isa.js"
   (:refer-clojure :exclude [isa? any? boolean? number? string? integer?])
   (:require
-   [medley.core :as m]
    [metabase.lib.types.constants :as lib.types.constants]
    [metabase.types]))
 
@@ -44,21 +43,6 @@
 
       :else false)))
 
-(defn ^:export field-type
-  "Return the category `column` belongs to.
-  The possible categories are the keys in [[metabase.lib.types.constants/type-hierarchies]]."
-  [column]
-  (m/find-first #(field-type? % column)
-                [::lib.types.constants/temporal
-                 ::lib.types.constants/location
-                 ::lib.types.constants/coordinate
-                 ::lib.types.constants/foreign_key
-                 ::lib.types.constants/primary_key
-                 ::lib.types.constants/boolean
-                 ::lib.types.constants/string
-                 ::lib.types.constants/string_like
-                 ::lib.types.constants/number]))
-
 (defn ^:export temporal?
   "Is `column` of a temporal type?"
   [column]
@@ -105,9 +89,9 @@
   (field-type? ::lib.types.constants/category column))
 
 (defn ^:export location?
-  "Is `column` of a location type?"
+  "Is `column` a location?"
   [column]
-  (field-type? ::lib.types.constants/location column))
+  (clojure.core/isa? (:semantic-type column) :type/Location))
 
 (defn ^:export description?
   "Is `column` a description?"
diff --git a/src/metabase/xrays/automagic_dashboards/filters.clj b/src/metabase/xrays/automagic_dashboards/filters.clj
index 563d155c0dc9..9f5e066659d3 100644
--- a/src/metabase/xrays/automagic_dashboards/filters.clj
+++ b/src/metabase/xrays/automagic_dashboards/filters.clj
@@ -68,8 +68,7 @@
 
 (defn- filter-for-card
   [card field]
-  (some->> ((:fk-map field) (:table_id card))
-           (vector :dimension)))
+  [:dimension ((:fk-map field) (:table_id card)) {:stage-number 0}])
 
 (defn- add-filter
   [dashcard filter-id field]
@@ -84,14 +83,29 @@
       (nil? (:card dashcard)) dashcard
       mappings                (update dashcard :parameter_mappings concat mappings))))
 
-(defn- filter-type
-  "Return filter type for a given field."
-  [{:keys [semantic_type] :as field}]
+(defn- filter-type-info
+  "Return parameter type and section id for a given field."
+  [{:keys [effective_type semantic_type] :as _field}]
   (cond
-    (temporal? field)                   "date/all-options"
-    (isa? semantic_type :type/State)    "location/state"
-    (isa? semantic_type :type/Country)  "location/country"
-    (isa? semantic_type :type/Category) "category"))
+    (or (isa? effective_type :type/Date) (isa? effective_type :type/DateTime))
+    {:type "date/all-options"
+     :sectionId "date"}
+
+    (or (isa? effective_type :type/Text) (isa? effective_type :type/TextLike))
+    {:type "string/="
+     :sectionId (if (isa? semantic_type :type/Address) "location" "string")}
+
+    (isa? effective_type :type/Number)
+    (if (or (isa? semantic_type :type/PK) (isa? semantic_type :type/FK))
+      {:type "id"
+       :sectionId "id"}
+      {:type "number/="
+       :sectionId "number"})
+
+    ;; TODO this needs to be `boolean/=` once we introduce boolean parameters in #57435
+    (isa? effective_type :type/Boolean)
+    {:type "string/="
+     :sectionId "string"}))
 
 (def ^:private ^{:arglists '([dimensions])} remove-unqualified
   (partial remove (fn [{:keys [fingerprint]}]
@@ -115,15 +129,16 @@
             (let [filter-id     (-> candidate ((juxt :id :name :unit)) hash str)
                   candidate     (assoc candidate :fk-map (build-fk-map fks candidate))
                   dashcards     (:dashcards dashboard)
-                  dashcards-new (keep #(add-filter % filter-id candidate) dashcards)]
-              ;; Only add filters that apply to all cards.
-              (if (= (count dashcards) (count dashcards-new))
+                  dashcards-new (keep #(add-filter % filter-id candidate) dashcards)
+                  filter-info   (filter-type-info candidate)]
+              ;; Only add filters that apply to all cards and when we have a parameter type for the field
+              (if (and (= (count dashcards) (count dashcards-new)) (some? filter-info))
                 (-> dashboard
                     (assoc :dashcards dashcards-new)
-                    (update :parameters conj {:id   filter-id
-                                              :type (filter-type candidate)
-                                              :name (:display_name candidate)
-                                              :slug (:name candidate)}))
+                    (update :parameters conj (merge {:id   filter-id
+                                                     :name (:display_name candidate)
+                                                     :slug (:name candidate)}
+                                                    filter-info)))
                 dashboard)))
           dashboard))))
 
diff --git a/test/metabase/lib/fe_util_test.cljc b/test/metabase/lib/fe_util_test.cljc
index 9a228ba7c29d..4649017f9a46 100644
--- a/test/metabase/lib/fe_util_test.cljc
+++ b/test/metabase/lib/fe_util_test.cljc
@@ -13,7 +13,6 @@
    [metabase.lib.ref :as lib.ref]
    [metabase.lib.test-metadata :as meta]
    [metabase.lib.test-util :as lib.tu]
-   [metabase.lib.types.isa :as lib.types.isa]
    [metabase.util.malli :as mu]
    [metabase.util.number :as u.number]
    [metabase.util.time :as u.time]))
@@ -40,7 +39,7 @@
                                       :inside (lib/expression-clause short-op [col 12 34 56 78 90] {})
                                       (:< :>) (lib/expression-clause short-op [col col] {})
                                       (lib/expression-clause short-op [col 123] {}))]]
-      (testing (str short-op " with " (lib.types.isa/field-type col))
+      (testing (str short-op " with " (:name col))
         (is (=? {:lib/type :mbql/expression-parts
                  :operator short-op
                  :args vector?}
diff --git a/test/metabase/lib/filter_test.cljc b/test/metabase/lib/filter_test.cljc
index bba86f87c9bc..345521aac388 100644
--- a/test/metabase/lib/filter_test.cljc
+++ b/test/metabase/lib/filter_test.cljc
@@ -12,7 +12,6 @@
    [metabase.lib.test-metadata :as meta]
    [metabase.lib.test-util :as lib.tu]
    [metabase.lib.test-util.matrix :as matrix]
-   [metabase.lib.types.isa :as lib.types.isa]
    [metabase.lib.util :as lib.util]
    [metabase.util :as u]
    [metabase.util.number :as u.number]))
@@ -354,7 +353,7 @@
                                   :inside (lib/filter-clause op col 12 34 56 78 90)
                                   (:< :>) (lib/filter-clause op col col)
                                   (lib/filter-clause op col 123))]]
-      (testing (str (:short op) " with " (lib.types.isa/field-type col))
+      (testing (str (:short op) " with " (:name col))
         (is (= op
                (lib/filter-operator query filter-clause)))))))
 
diff --git a/test/metabase/lib/types/isa_test.cljc b/test/metabase/lib/types/isa_test.cljc
index 9ff530f33da6..257cd17c4a48 100644
--- a/test/metabase/lib/types/isa_test.cljc
+++ b/test/metabase/lib/types/isa_test.cljc
@@ -5,7 +5,6 @@
    [metabase.lib.core :as lib]
    [metabase.lib.test-metadata :as meta]
    [metabase.lib.test-util :as lib.tu]
-   [metabase.lib.types.constants :as lib.types.constants]
    [metabase.lib.types.isa :as lib.types.isa]))
 
 #?(:cljs (comment metabase.test-runner.assert-exprs.approximately-equal/keep-me))
@@ -91,56 +90,6 @@
                 :effective-type :type/BigInteger}]
               (filter lib.types.isa/numeric? orderable-columns))))))
 
-(deftest ^:parallel field-type-test
-  ;; should fall back to `:base-type` if `:effective-type` isn't present.
-  (doseq [base-or-effective-type-key [:effective-type :base-type]]
-    (testing "temporal"
-      (are [typ] (= ::lib.types.constants/temporal (lib.types.isa/field-type {base-or-effective-type-key typ}))
-        :type/Date :type/DateTime :type/Time))
-    (testing "numeric"
-      (are [typ] (= ::lib.types.constants/number (lib.types.isa/field-type {base-or-effective-type-key typ}))
-        :type/BigInteger :type/Integer :type/Float :type/Decimal))
-    (testing "string"
-      (are [typ] (= ::lib.types.constants/string (lib.types.isa/field-type {base-or-effective-type-key typ}))
-        :type/Text :type/MySQLEnum))
-    (testing "types of string"
-      (are [typ] (= ::lib.types.constants/string (lib.types.isa/field-type {base-or-effective-type-key :type/Text
-                                                                            :semantic-type typ}))
-        :type/Name :type/Description :type/UUID :type/URL))
-    (testing "primary key"
-      (is (= ::lib.types.constants/primary_key (lib.types.isa/field-type {base-or-effective-type-key :type/Integer
-                                                                          :semantic-type             :type/PK}))))
-    (testing "foreign key"
-      (is (= ::lib.types.constants/foreign_key (lib.types.isa/field-type {base-or-effective-type-key :type/Integer
-                                                                          :semantic-type             :type/FK}))))
-    (testing "boolean"
-      (is (= ::lib.types.constants/boolean (lib.types.isa/field-type {base-or-effective-type-key :type/Boolean}))))
-    (testing "location"
-      (are [typ] (= ::lib.types.constants/location (lib.types.isa/field-type {:semantic-type typ}))
-        :type/City :type/Country))
-    (testing "coordinate"
-      (are [typ] (= ::lib.types.constants/coordinate (lib.types.isa/field-type {:semantic-type typ}))
-        :type/Latitude :type/Longitude))
-    (testing "string like"
-      (are [typ] (= ::lib.types.constants/string_like (lib.types.isa/field-type {base-or-effective-type-key typ}))
-        :type/TextLike :type/IPAddress))
-    (testing "number, regardless of the semantic type is"
-      (are [typ] (= ::lib.types.constants/number (lib.types.isa/field-type {base-or-effective-type-key :type/Float
-                                                                            :semantic-type typ}))
-        :type/Name :type/Category))
-    (testing "boolean, regardless of the semantic type"
-      (is (= ::lib.types.constants/boolean (lib.types.isa/field-type {base-or-effective-type-key :type/Boolean
-                                                                      :semantic-type             :type/Category})))))
-  (testing "unexpected things"
-    (are [column] (nil? (lib.types.isa/field-type column))
-      {:effective-type "DERP DERP DERP"}
-      {:semantic-type "DERP DERP DERP"}
-      {:effective-type nil}
-      {:semantic-type nil}
-      "DERP DERP DERP"
-      :type/Category
-      nil)))
-
 (deftest ^:parallel type-predicate-test
   (letfn [(column [x]
             (cond
@@ -161,6 +110,7 @@
              {:pred #'lib.types.isa/category?,           :positive :type/Category,          :negative :type/Boolean}
              {:pred #'lib.types.isa/category?,           :positive :type/Company,           :negative :type/URL}
              {:pred #'lib.types.isa/location?,           :positive :type/Address,           :negative :type/Number}
+             {:pred #'lib.types.isa/location?,           :positive :type/Latitude           :negative :type/Category}
              {:pred #'lib.types.isa/description?,        :positive :type/Description,       :negative :type/City}
              {:pred #'lib.types.isa/foreign-key?,        :positive :type/FK,                :negative :type/ZipCode}
              {:pred #'lib.types.isa/primary-key?,        :positive :type/PK,                :negative :type/ZipCode}
diff --git a/test/metabase/xrays/api/automagic_dashboards_test.clj b/test/metabase/xrays/api/automagic_dashboards_test.clj
index e48e138586a1..cad50f4b2eaf 100644
--- a/test/metabase/xrays/api/automagic_dashboards_test.clj
+++ b/test/metabase/xrays/api/automagic_dashboards_test.clj
@@ -69,6 +69,30 @@
   (testing "GET /api/automagic-dashboards/table/:id/rule/example/indepth"
     (is (some? (api-call! "table/%s/rule/example/indepth" [(mt/id :venues)])))))
 
+(deftest xray-dashboard-parameter-types-test
+  (testing "Should set correct parameter types"
+    (mt/dataset test-data
+      (is (=? {:parameters [{:name      "Created At"
+                             :type      "date/all-options"
+                             :sectionId "date"}
+                            {:name      "Category"
+                             :type      "string/="
+                             :sectionId "string"}]}
+              (mt/user-http-request :crowberto :get 200 (format "automagic-dashboards/table/%d" (mt/id :products)))))
+      (is (=? {:parameters [{:name      "Created At"
+                             :type      "date/all-options"
+                             :sectionId "date"}
+                            {:name      "Source"
+                             :type      "string/="
+                             :sectionId "string"}
+                            {:name      "State"
+                             :type      "string/="
+                             :sectionId "location"}
+                            {:name      "City"
+                             :type      "string/="
+                             :sectionId "location"}]}
+              (mt/user-http-request :crowberto :get 200 (format "automagic-dashboards/table/%d" (mt/id :people))))))))
+
 (deftest save-table-xray-dashboard-test
   (testing "Should be able to save a Dashboard generated by X-Rays"
     (mt/dataset test-data
diff --git a/test/metabase/xrays/automagic_dashboards/core_test.clj b/test/metabase/xrays/automagic_dashboards/core_test.clj
index 3528c63d9382..414a6d603c0d 100644
--- a/test/metabase/xrays/automagic_dashboards/core_test.clj
+++ b/test/metabase/xrays/automagic_dashboards/core_test.clj
@@ -229,8 +229,8 @@
     (testing "mbql queries have parameter mappings with field ids"
       (let [table (t2/select-one :model/Table :id (mt/id :products))
             dashboard (magic/automagic-analysis table {})
-            expected-targets (mt/$ids #{[:dimension $products.category]
-                                        [:dimension $products.created_at]})
+            expected-targets (mt/$ids #{[:dimension $products.category {:stage-number 0}]
+                                        [:dimension $products.created_at {:stage-number 0}]})
             actual-targets (into #{}
                                  (comp (mapcat :parameter_mappings)
                                        (map :target))
@@ -246,8 +246,8 @@
           (let [dashboard (magic/automagic-analysis card {})
                 ;; i'm not sure why category isn't picked here
                 expected-targets #{[:dimension
-                                    [:field "CREATED_AT"
-                                     {:base-type :type/DateTimeWithLocalTZ}]]}
+                                    [:field "CREATED_AT" {:base-type :type/DateTimeWithLocalTZ}]
+                                    {:stage-number 0}]}
                 actual-targets (into #{}
                                      (comp (mapcat :parameter_mappings)
                                            (map :target))


Reviews:
user: login: ranquild
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T13:11:47Z
user: login: ranquild
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T13:12:19Z
user: login: uladzimirdev
body: FE part LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T19:28:41Z

Review comments:
diff_hunk: @@ -89,7 +89,7 @@ export function columnFilterForParameter(
       isBoolean: Lib.isBoolean(column),
       isTemporal: Lib.isTemporal(column),
       isID: Lib.isID(column),
-      isLocation: Lib.isLocation(column),
path: frontend/src/metabase-lib/v1/parameters/utils/filters.ts
user: login: ranquild
body: Note - this is a noop change because of the previous bug in `isLocation`
created_at: 2025-05-07T13:11:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -44,21 +43,6 @@
 
       :else false)))
 
-(defn ^:export field-type
path: src/metabase/lib/types/isa.cljc
user: login: ranquild
body: Unused!
created_at: 2025-05-07T13:12:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: trunk-io[bot]
created_at: 2025-05-07T02:48:20Z
body: <!-- Trunk Test Analytics -->


[![Static Badge](https://raster.shields.io/badge/126794_tests-bf40cdec-purple)](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment) &ensp; [![Static Badge](https://raster.shields.io/badge/7-failed-crimson)](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/2-flaky-red)](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://raster.shields.io/badge/0-quarantined-yellow)](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=QUARANTINED)


| Failed Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>scenarios > setup should ignore snowplow failures and work as normal</code> | Snowplow retry timed out due to null value. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE&runId=03cfe635-ae08-5563-8579-a56fc496e412) |
| <code>scenarios > setup should ignore snowplow failures and work as normal</code> | Snowplow retry timed out due to null value. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE&runId=0892a41e-d78c-58d5-9226-396594835696) |
| <code>metric-comparison-test</code> | The result of the aggregation with filter is the same as the result of the metric with filter for :percentile, which is not expected. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE&runId=2684b931-bf98-5fd3-bfa0-a66aed7c793d) |
| <code>get-fks-test</code> | Expected a single FK from CHECKINS.USER\_ID -> USERS.ID, but actual result was different. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE&runId=73a1f133-eebd-512d-b0a2-086698dd6258) |

[... and 3 more](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE)


| Flaky Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>scenarios > setup should pre-fill user info for hosted instances (infra-frontend\#1109)</code> | The element '<input\#7.emotion-li5g9t.ekh1rgw1>' timed out while waiting to be focused. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE&runId=9ecdb2b3-7b19-5e77-b7b4-da8e10758b14) |
| <code>fetch-token-does-not-call-db-when-cached</code> | failure\_text: token\_check\_test.clj:164No DB calls are made for the user count when checking token status if the status is cachedexpected: (= 6 (... | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment&runConclusion=FAILURE&runId=ce5ea9a5-2990-5d56-ad1d-2931bb1a8664) |
<sub>

[View Full Report ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/57677?repo=metabase/metabase&commitHash=bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a&utm_source=gh-comment) ⋅ [Docs](https://docs.trunk.io/flaky-tests?utm_source=gh-comment)

</sub>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-08T00:35:41Z
body: 👋 Deploying a preview environment for commit bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a.
✅ Preview:
    https://pr57677.coredev.metabase.com
🗒️ [login instructions](https://www.notion.so/metabase/Ephemeral-PR-Environments-c74a3710c87a460dbf4fc0007a001458)
<!-- Sticky Pull Request Comment -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-08T00:44:07Z
body: 
  ## e2e tests failed on `bf40cdec34bb0e06fe9183f55baf3e8a64ecd75a-2`

  [e2e test run](https://github.com/metabase/metabase/actions/runs/14895882618?pr=57677)

  File | Test Name
  ---- | ---------
  `setup.cy.spec.ts` | (_flaky_) scenarios > setup > should pre-fill user info for hosted instances (infra-frontend#1109)
`setup.cy.spec.ts` | (_flaky_) scenarios > setup > should send snowplow events
`setup.cy.spec.ts` | (_flaky_) scenarios > setup > should ignore snowplow failures and work as normal

author_association: CONTRIBUTOR