Skip to content
Snippets Groups Projects
Unverified Commit 219910fd authored by Dalton's avatar Dalton Committed by GitHub
Browse files

Refactor `metabase/parameters/utils/dashboards` functions to be less coupled to Fields (#22964)

* refactor dashboard utils

Fix e2e test

* rmv unused arg
parent 47b7ac9e
No related branches found
No related tags found
No related merge requests found
Showing
with 423 additions and 384 deletions
......@@ -18,8 +18,7 @@ import {
createParameter,
setParameterName as setParamName,
setParameterDefaultValue as setParamDefaultValue,
getMappingsByParameter,
getDashboardParametersWithFieldMetadata,
getDashboardUiParameters,
getParametersMappedToDashcard,
getFilteringParameterValuesMap,
getParameterValuesSearchKey,
......@@ -784,13 +783,7 @@ export const fetchDashboard = createThunkAction(FETCH_DASHBOARD, function(
}
const metadata = getMetadata(getState());
const mappingsByParameter = getMappingsByParameter(metadata, result);
const parameters = getDashboardParametersWithFieldMetadata(
metadata,
result,
mappingsByParameter,
);
const parameters = getDashboardUiParameters(result, metadata);
const parameterValuesById = preserveParameters
? getParameterValues(getState())
......
......@@ -14,7 +14,6 @@ import Question from "metabase-lib/lib/Question";
import {
getEditingParameter,
getMappingsByParameter,
getParameterTarget,
makeGetParameterMappingOptions,
} from "../selectors";
......@@ -43,7 +42,6 @@ const mapStateToProps = (state, props) => ({
editingParameter: getEditingParameter(state, props),
target: getParameterTarget(state, props),
mappingOptions: makeGetParameterMappingOptions()(state, props),
mappingsByParameter: getMappingsByParameter(state, props),
metadata: getMetadata(state),
});
......@@ -57,7 +55,6 @@ DashCardCardParameterMapper.propTypes = {
editingParameter: PropTypes.object.isRequired,
target: PropTypes.object,
mappingOptions: PropTypes.array.isRequired,
mappingsByParameter: PropTypes.object.isRequired,
metadata: PropTypes.object.isRequired,
setParameterMapping: PropTypes.func.isRequired,
};
......@@ -67,7 +64,6 @@ function DashCardCardParameterMapper({
dashcard,
editingParameter,
target,
mappingsByParameter,
mappingOptions,
metadata,
setParameterMapping,
......
......@@ -5,8 +5,7 @@ import { getMetadata } from "metabase/selectors/metadata";
import { LOAD_COMPLETE_FAVICON } from "metabase/hoc/Favicon";
import {
getMappingsByParameter as _getMappingsByParameter,
getDashboardParametersWithFieldMetadata,
getDashboardUiParameters,
getFilteringParameterValuesMap,
getParameterValuesSearchKey,
} from "metabase/parameters/utils/dashboards";
......@@ -135,15 +134,14 @@ export const getParameterTarget = createSelector(
},
);
export const getMappingsByParameter = createSelector(
[getMetadata, getDashboardComplete],
_getMappingsByParameter,
);
/** Returns the dashboard's parameters objects, with field_id added, if appropriate */
export const getParameters = createSelector(
[getMetadata, getDashboard, getMappingsByParameter],
getDashboardParametersWithFieldMetadata,
[getDashboardComplete, getMetadata],
(dashboard, metadata) => {
if (!dashboard || !metadata) {
return [];
}
return getDashboardUiParameters(dashboard, metadata);
},
);
export const makeGetParameterMappingOptions = () => {
......
......@@ -22,7 +22,19 @@ const STATE = {
},
dashcards: {
0: {
card: { id: 0, dataset_query: { type: "query", query: {} } },
card: {
id: 0,
dataset_query: {
type: "native",
query: {
"template-tags": {
foo: {
type: "text",
},
},
},
},
},
parameter_mappings: [],
},
1: {
......@@ -50,61 +62,74 @@ describe("dashboard/selectors", () => {
it("should work with no parameters", () => {
expect(getParameters(STATE)).toEqual([]);
});
it("should not include field id with no mappings", () => {
it("should return a parameter", () => {
const state = chain(STATE)
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], { id: 1 })
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], {
id: 1,
type: "foo",
})
.value();
expect(getParameters(state)).toEqual([
{
id: 1,
fields: [],
field_ids: [],
field_id: null,
hasOnlyFieldTargets: true,
type: "foo",
},
]);
});
it("should not include field id with one mapping, no field id", () => {
it("should return a FieldFilterUiParameter mapped to a variable template tag", () => {
const state = chain(STATE)
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], { id: 1 })
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], {
id: 1,
type: "string/=",
})
.assocIn(["dashboard", "dashcards", 0, "parameter_mappings", 0], {
card_id: 0,
parameter_id: 1,
target: ["variable", ["template-tag", "foo"]],
})
.value();
expect(getParameters(state)).toEqual([
{
id: 1,
fields: [],
field_ids: [],
field_id: null,
type: "string/=",
hasOnlyFieldTargets: false,
fields: [],
},
]);
});
it("should include field id with one mappings, with field id", () => {
it("should return a FieldFilterUiParameter mapped to a field", () => {
const state = chain(STATE)
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], { id: 1 })
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], {
id: 1,
type: "string/=",
})
.assocIn(["dashboard", "dashcards", 0, "parameter_mappings", 0], {
card_id: 0,
parameter_id: 1,
target: ["dimension", ["field", 1, null]],
})
.value();
expect(getParameters(state)).toEqual([
{
id: 1,
fields: [expect.any(Field)],
field_ids: [1],
field_id: 1,
type: "string/=",
hasOnlyFieldTargets: true,
fields: [expect.any(Field)],
},
]);
});
it("should include field id with two mappings, with same field id", () => {
it("should return a FieldFilterUiParameter with two mappings to the same field", () => {
const state = chain(STATE)
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], { id: 1 })
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], {
id: 1,
type: "string/=",
})
.assocIn(["dashboard", "dashcards", 0, "parameter_mappings", 0], {
card_id: 0,
parameter_id: 1,
......@@ -116,19 +141,23 @@ describe("dashboard/selectors", () => {
target: ["dimension", ["field", 1, null]],
})
.value();
expect(getParameters(state)).toEqual([
{
id: 1,
type: "string/=",
fields: [expect.any(Field)],
field_ids: [1],
field_id: 1,
hasOnlyFieldTargets: true,
},
]);
});
it("should include field id with two mappings, one with field id, one without", () => {
it("should return a FieldFilterUiParameter that has mappings to a field and a template tag variable", () => {
const state = chain(STATE)
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], { id: 1 })
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], {
id: 1,
type: "string/=",
})
.assocIn(["dashboard", "dashcards", 0, "parameter_mappings", 0], {
card_id: 0,
parameter_id: 1,
......@@ -140,19 +169,23 @@ describe("dashboard/selectors", () => {
target: ["variable", ["template-tag", "foo"]],
})
.value();
expect(getParameters(state)).toEqual([
{
id: 1,
type: "string/=",
fields: [expect.any(Field)],
field_ids: [1],
field_id: 1,
hasOnlyFieldTargets: false,
},
]);
});
it("should include all field ids with two mappings, with different field ids", () => {
it("should return a FieldFilterUiParameter with two mappings to two different fields", () => {
const state = chain(STATE)
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], { id: 1 })
.assocIn(["dashboard", "dashboards", 0, "parameters", 0], {
id: 1,
type: "string/=",
})
.assocIn(["dashboard", "dashcards", 0, "parameter_mappings", 0], {
card_id: 0,
parameter_id: 1,
......@@ -164,12 +197,12 @@ describe("dashboard/selectors", () => {
target: ["dimension", ["field", 2, null]],
})
.value();
expect(getParameters(state)).toEqual([
{
id: 1,
type: "string/=",
fields: [expect.any(Field), expect.any(Field)],
field_ids: [1, 2],
field_id: null,
hasOnlyFieldTargets: true,
},
]);
......
......@@ -143,9 +143,9 @@ class OtherParameterList extends React.Component {
}
const { parameter, otherParameters } = this.props;
const filtered = parameter.field_ids;
const filtered = parameter.fields.map(field => field.id);
const parameterForId = otherParameters.find(p => p.id === id);
const filtering = parameterForId.field_ids;
const filtering = parameterForId.fields.map(field => field.id);
if (filtered.length === 0 || filtering.length === 0) {
const param = filtered.length === 0 ? parameter : parameterForId;
const error = t`To view this, ${param.name} must be connected to at least one field.`;
......
import _ from "underscore";
import { setIn } from "icepick";
import Question from "metabase-lib/lib/Question";
import { generateParameterId } from "metabase/parameters/utils/parameter-id";
import { getParameterTargetField } from "metabase/parameters/utils/targets";
import { isFieldFilterParameter } from "metabase/parameters/utils/parameter-type";
import { slugify } from "metabase/lib/formatting";
import {
UiParameter,
......@@ -13,7 +13,8 @@ import {
import {
Parameter,
ParameterMappingOptions,
ParameterTarget,
ParameterDimensionTarget,
ParameterVariableTarget,
} from "metabase-types/types/Parameter";
import {
Dashboard,
......@@ -24,26 +25,9 @@ import { SavedCard } from "metabase-types/types/Card";
import Metadata from "metabase-lib/lib/metadata/Metadata";
import Field from "metabase-lib/lib/metadata/Field";
type ExtendedMapping = {
parameter_id: string;
type ExtendedMapping = DashboardParameterMapping & {
dashcard_id: number;
card_id: number;
field_id: string | number | undefined;
field: Field | null | undefined;
target: ParameterTarget;
};
type ExtendedFieldFilterUiParameter = FieldFilterUiParameter & {
field_id: number | string | null;
field_ids: (number | string)[];
};
type NestedMappingsMap = {
[parameterId: string]: {
[dashcardId: number]: {
[cardId: number]: ExtendedMapping;
};
};
card: SavedCard;
};
export function createParameter(
......@@ -116,118 +100,83 @@ export function isDashboardParameterWithoutMapping(
return parameterExistsOnDashboard && !parameterHasMapping;
}
export function getMappingTargetField(
card: SavedCard,
mapping: DashboardParameterMapping,
metadata: Metadata,
) {
if (!card?.dataset_query) {
return null;
}
const question = new Question(card, metadata);
const field = getParameterTargetField(mapping.target, metadata, question);
return field;
function getMappings(ordered_cards: DashboardOrderedCard[]): ExtendedMapping[] {
return ordered_cards.flatMap(dashcard => {
const { parameter_mappings, card, series } = dashcard;
const cards = [card, ...(series || [])];
return (parameter_mappings || [])
.map(parameter_mapping => {
const card = _.findWhere(cards, { id: parameter_mapping.card_id });
return card
? {
...parameter_mapping,
dashcard_id: dashcard.id,
card,
}
: null;
})
.filter((mapping): mapping is ExtendedMapping => mapping != null);
});
}
function getMapping(
dashcard: DashboardOrderedCard,
export function getDashboardUiParameters(
dashboard: Dashboard,
metadata: Metadata,
): ExtendedMapping[] {
const cards = [dashcard.card, ...(dashcard.series || [])];
return (dashcard.parameter_mappings || []).map(mapping => {
const card = _.findWhere(cards, { id: mapping.card_id });
const field = card ? getMappingTargetField(card, mapping, metadata) : null;
): UiParameter[] {
const { parameters, ordered_cards } = dashboard;
const mappings = getMappings(ordered_cards);
const uiParameters: UiParameter[] = (parameters || []).map(parameter => {
if (isFieldFilterParameter(parameter)) {
return buildFieldFilterUiParameter(parameter, mappings, metadata);
}
return {
...mapping,
parameter_id: mapping.parameter_id,
dashcard_id: dashcard.id,
card_id: mapping.card_id,
field_id: field?.getId() ?? field?.name,
field,
...parameter,
};
});
}
function getMappings(dashboard: Dashboard, metadata: Metadata) {
const mappings = dashboard.ordered_cards.flatMap(dashcard =>
getMapping(dashcard, metadata),
);
return mappings;
return uiParameters;
}
export function getMappingsByParameter(
function buildFieldFilterUiParameter(
parameter: Parameter,
mappings: ExtendedMapping[],
metadata: Metadata,
dashboard: Dashboard,
): NestedMappingsMap {
if (!dashboard) {
return {};
}
const mappings = getMappings(dashboard, metadata);
const mappingsByParameterIdByDashcardIdByCardId = mappings.reduce(
(map, mapping) =>
setIn(
map,
[mapping.parameter_id, mapping.dashcard_id, mapping.card_id],
mapping,
),
{},
): FieldFilterUiParameter {
const mappingsForParameter = mappings.filter(
mapping => mapping.parameter_id === parameter.id,
);
const mappedFields = mappingsForParameter.map(mapping => {
const { target, card } = mapping;
return getTargetField(target, card, metadata);
});
const fields = mappedFields.filter((field): field is Field => field != null);
const hasOnlyFieldTargets =
mappedFields.length !== 0 && mappedFields.length === fields.length;
const uniqueFieldsWithFKResolved = _.uniq(
fields.map(field => field.target ?? field),
field => field.id,
);
return mappingsByParameterIdByDashcardIdByCardId;
return {
...parameter,
fields: uniqueFieldsWithFKResolved,
hasOnlyFieldTargets,
};
}
export function getDashboardParametersWithFieldMetadata(
export function getTargetField(
target: ParameterVariableTarget | ParameterDimensionTarget,
card: SavedCard,
metadata: Metadata,
dashboard: Dashboard,
mappingsByParameter: NestedMappingsMap,
): ExtendedFieldFilterUiParameter[] {
return ((dashboard && dashboard.parameters) || []).map(parameter => {
const mappings: ExtendedMapping[] = _.flatten(
_.map(mappingsByParameter[parameter.id] || {}, _.values),
);
// we change out widgets if a parameter is connected to non-field targets
const hasOnlyFieldTargets = mappings.every(x => x.field_id != null);
const fields: Field[] = _.uniq(
mappings
.map(mapping => mapping.field)
.filter((field): field is Field => field != null)
.map(field => field.target ?? field),
field => field.id,
);
// get the unique list of field IDs these mappings reference
const fieldIds: (string | number)[] = _.uniq(
mappings
.map(m => m.field_id)
.filter((fieldId): fieldId is string | number => fieldId != null),
);
const fieldIdsWithFKResolved = _.uniq(
fieldIds
.map(fieldId => {
const maybeField: Field | undefined = metadata.field(fieldId);
return maybeField?.target?.getId() ?? maybeField?.getId();
})
.filter((maybeFieldId): maybeFieldId is number => maybeFieldId != null),
);
) {
if (!card?.dataset_query) {
return null;
}
return {
...parameter,
field_ids: fieldIds,
// if there's a single uniqe field (accounting for FKs) then
// include it as the one true field_id
field_id:
fieldIdsWithFKResolved.length === 1 ? fieldIdsWithFKResolved[0] : null,
fields,
hasOnlyFieldTargets,
};
});
const question = new Question(card, metadata);
const field = getParameterTargetField(target, metadata, question);
return field ?? null;
}
export function getParametersMappedToDashcard(
......@@ -257,13 +206,11 @@ export function hasMatchingParameters({
dashcardId,
cardId,
parameters,
metadata,
}: {
dashboard: Dashboard;
dashcardId: number;
cardId: number;
parameters: Parameter[];
metadata: Metadata;
}) {
const dashcard = _.findWhere(dashboard.ordered_cards, {
id: dashcardId,
......@@ -273,8 +220,13 @@ export function hasMatchingParameters({
return false;
}
const mappings = getMappings(dashboard.ordered_cards);
const mappingsForDashcard = mappings.filter(
mapping => mapping.dashcard_id === dashcardId,
);
const dashcardMappingsByParameterId = _.indexBy(
getMapping(dashcard, metadata),
mappingsForDashcard,
"parameter_id",
);
......
......@@ -4,20 +4,18 @@ import {
setParameterDefaultValue,
hasMapping,
isDashboardParameterWithoutMapping,
getMappingsByParameter,
getParametersMappedToDashcard,
hasMatchingParameters,
getFilteringParameterValuesMap,
getParameterValuesSearchKey,
getMappingTargetField,
getTargetField,
getDashboardUiParameters,
} from "metabase/parameters/utils/dashboards";
import { metadata } from "__support__/sample_database_fixture";
import DASHBOARD_WITH_BOOLEAN_PARAMETER from "./fixtures/dashboard-with-boolean-parameter.json";
import Field from "metabase-lib/lib/metadata/Field";
describe("meta/Dashboard", () => {
import { PRODUCTS, metadata } from "__support__/sample_database_fixture";
describe("metabase/parameters/utils/dashboards", () => {
describe("createParameter", () => {
it("should create a new parameter using the given parameter option", () => {
expect(
......@@ -287,119 +285,6 @@ describe("meta/Dashboard", () => {
});
});
describe("getMappingsByParameter", () => {
let metadata;
let dashboard;
beforeEach(() => {
metadata = {
fields: {
120: new Field({
values: {
values: [false, true],
human_readable_values: [],
field_id: 120,
},
id: 120,
table_id: 6,
display_name: "CouponUsed",
base_type: "type/Boolean",
semantic_type: null,
has_field_values: "list",
name_field: null,
dimensions: {},
fieldValues: () => [],
}),
134: new Field({
values: {
values: [false, true],
human_readable_values: [],
field_id: 134,
},
id: 134,
table_id: 8,
display_name: "Bool",
base_type: "type/Boolean",
semantic_type: "type/Category",
has_field_values: "list",
name_field: null,
dimensions: {},
fieldValues: () => [],
}),
},
field(id) {
return this.fields[id];
},
tables: {
6: {
id: 6,
},
8: {
id: 8,
},
},
table(id) {
return this.tables[id];
},
};
dashboard = DASHBOARD_WITH_BOOLEAN_PARAMETER;
});
it("should generate a map of parameter mappings with added field metadata", () => {
const mappings = getMappingsByParameter(metadata, dashboard);
expect(mappings).toEqual({
parameter1: {
"81": {
"56": {
card_id: 56,
dashcard_id: 81,
field: expect.any(Field),
field_id: 120,
parameter_id: "parameter1",
target: ["dimension", ["field", 120, null]],
},
},
"86": {
"59": {
card_id: 59,
dashcard_id: 86,
field: expect.any(Field),
field_id: 134,
parameter_id: "parameter1",
target: ["dimension", ["template-tag", "bbb"]],
},
},
"87": {
"62": {
card_id: 62,
dashcard_id: 87,
field: expect.any(Field),
field_id: "boolean",
parameter_id: "parameter1",
target: [
"dimension",
["field", "boolean", { "base-type": "type/Boolean" }],
],
},
},
},
});
expect(mappings.parameter1["81"]["56"].field.getPlainObject()).toEqual(
expect.objectContaining(metadata.field(120).getPlainObject()),
);
expect(mappings.parameter1["86"]["59"].field.getPlainObject()).toEqual(
expect.objectContaining(metadata.field(134).getPlainObject()),
);
expect(mappings.parameter1["87"]["62"].field.getPlainObject()).toEqual(
expect.objectContaining({
name: "boolean",
}),
);
});
});
describe("getParametersMappedToDashcard", () => {
const dashboard = {
parameters: [
......@@ -419,7 +304,7 @@ describe("meta/Dashboard", () => {
],
};
const dashboardWithNoParameters = {};
const dashboardWithNoParameters = { parameters: [] };
const dashcard = {
parameter_mappings: [
......@@ -466,8 +351,10 @@ describe("meta/Dashboard", () => {
{
id: 1,
card_id: 123,
card: { id: 123 },
parameter_mappings: [
{
card_id: 123,
parameter_id: "foo",
},
],
......@@ -502,8 +389,10 @@ describe("meta/Dashboard", () => {
{
id: 1,
card_id: 123,
card: { id: 123 },
parameter_mappings: [
{
card_id: 123,
parameter_id: "foo",
},
],
......@@ -511,8 +400,10 @@ describe("meta/Dashboard", () => {
{
id: 2,
card_id: 456,
card: { id: 456 },
parameter_mappings: [
{
card_id: 456,
parameter_id: "bar",
},
],
......@@ -544,11 +435,14 @@ describe("meta/Dashboard", () => {
{
id: 1,
card_id: 123,
card: { id: 123 },
parameter_mappings: [
{
card_id: 123,
parameter_id: "foo",
},
{
card_id: 123,
parameter_id: "bar",
},
],
......@@ -695,27 +589,19 @@ describe("meta/Dashboard", () => {
});
});
describe("getMappingTargetField", () => {
const mapping = {
parameter_id: "dbe38f17",
card_id: 1,
target: ["dimension", ["field", 4, null]],
};
describe("getTargetField", () => {
const target = ["dimension", ["field", 4, null]];
const metadata = {
field: jest.fn(),
};
it("should return null when not given a card", () => {
expect(getMappingTargetField(null, mapping, metadata)).toBe(null);
});
it("should return null when given a card without a `dataset_query`", () => {
const card = {
id: 1,
};
expect(getMappingTargetField(card, mapping, metadata)).toBe(null);
expect(getTargetField(target, card, metadata)).toBe(null);
});
it("should return the field that maps to the mapping target", () => {
......@@ -741,7 +627,217 @@ describe("meta/Dashboard", () => {
},
};
expect(getMappingTargetField(card, mapping, metadata)).toEqual(field);
expect(getTargetField(target, card, metadata)).toEqual(field);
});
});
describe("getDashboardUiParameters", () => {
const dashboard = {
id: 1,
ordered_cards: [
{
id: 1,
card_id: 123,
card: { id: 123, dataset_query: { type: "query" } },
series: [{ id: 789, dataset_query: { type: "query" } }],
parameter_mappings: [
{
card_id: 123,
parameter_id: "b",
target: ["breakout", 0],
},
{
card_id: 789,
parameter_id: "d",
target: ["dimension", ["field", PRODUCTS.RATING.id, null]],
},
{
card_id: 123,
parameter_id: "f",
target: ["dimension", ["field", PRODUCTS.TITLE.id, null]],
},
{
card_id: 123,
parameter_id: "g",
target: ["dimension", ["field", PRODUCTS.TITLE.id, null]],
},
],
},
{
id: 2,
card_id: 456,
card: {
id: 456,
dataset_query: {
type: "native",
native: {
query: "{{foo}}",
"template-tags": {
foo: {
type: "text",
},
bar: {
type: "dimension",
"widget-type": "string/contains",
dimension: ["field", PRODUCTS.TITLE.id, null],
},
},
},
},
},
parameter_mappings: [
{
card_id: 456,
parameter_id: "e",
target: ["variable", "foo"],
},
{
card_id: 456,
parameter_id: "f",
target: ["dimension", ["template-tag", "bar"]],
},
{
card_id: 456,
parameter_id: "h",
target: ["variable", "foo"],
},
],
},
{
id: 3,
card_id: 999,
card: { id: 999, dataset_query: { type: "query" } },
parameter_mappings: [
{
card_id: 999,
parameter_id: "g",
target: ["dimension", ["field", PRODUCTS.CATEGORY.id, null]],
},
{
card_id: 999,
parameter_id: "h",
target: ["dimension", ["field", PRODUCTS.CATEGORY.id, null]],
},
],
},
{
id: 4,
card_id: 888,
card: { id: 888, dataset_query: { type: "query" } },
parameter_mappings: [],
},
],
parameters: [
// unmapped, not field filter
{
id: "a",
slug: "slug-a",
type: "foo",
},
// mapped, not field filter
{
id: "b",
slug: "slug-b",
type: "granularity",
default: ["day"],
},
// unmapped, field filter
{
id: "c",
slug: "slug-c",
type: "string/=",
},
// mapped, field filter
{
id: "d",
slug: "slug-d",
type: "number/=",
default: [1, 2, 3],
},
// mapped to variable, field filter
{
id: "e",
slug: "slug-e",
type: "category",
},
// field filter, mapped to two cards, same field
{
id: "f",
slug: "slug-f",
type: "string/contains",
},
// field filter, mapped to two, different fields
{
id: "g",
slug: "slug-g",
type: "string/starts-with",
},
// field filter, mapped to field and variable
{
id: "h",
slug: "slug-h",
type: "string/=",
},
],
};
it("should return a list of UiParameter objects from the given dashboard", () => {
expect(getDashboardUiParameters(dashboard, metadata)).toEqual([
{
id: "a",
slug: "slug-a",
type: "foo",
},
{
id: "b",
slug: "slug-b",
type: "granularity",
default: ["day"],
},
{
id: "c",
slug: "slug-c",
type: "string/=",
fields: [],
hasOnlyFieldTargets: false,
},
{
id: "d",
slug: "slug-d",
type: "number/=",
default: [1, 2, 3],
fields: [expect.any(Field)],
hasOnlyFieldTargets: true,
},
{
id: "e",
slug: "slug-e",
type: "category",
fields: [],
hasOnlyFieldTargets: false,
},
{
id: "f",
slug: "slug-f",
type: "string/contains",
fields: [expect.any(Field)],
hasOnlyFieldTargets: true,
},
{
id: "g",
slug: "slug-g",
type: "string/starts-with",
fields: [expect.any(Field), expect.any(Field)],
hasOnlyFieldTargets: true,
},
{
id: "h",
slug: "slug-h",
type: "string/=",
fields: [expect.any(Field)],
hasOnlyFieldTargets: false,
},
]);
});
});
});
......@@ -6,12 +6,6 @@ export function hasFieldValues(parameter) {
return parameter.fields.some(field => field.hasFieldValues());
}
export function getFieldIds(parameter) {
const { field_ids = [], field_id } = parameter;
const fieldIds = field_id ? [field_id] : field_ids;
return fieldIds.filter(id => typeof id === "number");
}
export function hasFields(parameter) {
const { fields } = parameter;
return Array.isArray(fields) && fields.length > 0;
......
import Field from "metabase-lib/lib/metadata/Field";
import {
hasFieldValues,
getFieldIds,
hasFields,
isOnlyMappedToFields,
} from "./fields";
import { hasFieldValues, hasFields, isOnlyMappedToFields } from "./fields";
describe("parameters/utils/fields", () => {
describe("hasFieldValues", () => {
......@@ -32,29 +27,6 @@ describe("parameters/utils/fields", () => {
});
});
describe("getFieldIds", () => {
it("should handle a parameter with no fields", () => {
expect(getFieldIds({})).toEqual([]);
});
it("should return number field ids", () => {
expect(getFieldIds({ field_ids: [1, 2, 3] })).toEqual([1, 2, 3]);
});
it("should filter out virtual field ids", () => {
expect(getFieldIds({ field_ids: [1, "two", 3] })).toEqual([1, 3]);
});
it("should favor the field_id prop for whatever reason", () => {
expect(
getFieldIds({
field_id: 1,
field_ids: [2, 3],
}),
).toEqual([1]);
});
});
describe("hasFields", () => {
it("should be false when the parameter has no fields", () => {
expect(hasFields({ fields: [] })).toBe(false);
......
......@@ -82,7 +82,6 @@ async function verifyMatchingDashcardAndParameters({
dashcardId,
cardId,
parameters,
metadata,
})
) {
dispatch(setErrorPage({ status: 403 }));
......
import { restore } from "__support__/e2e/cypress";
import { restore, popover } from "__support__/e2e/cypress";
import { SAMPLE_DATABASE } from "__support__/e2e/cypress_sample_database";
const { ORDERS, ORDERS_ID, PRODUCTS, PRODUCTS_ID } = SAMPLE_DATABASE;
const filterDetails = {
name: "ID",
name: "ID Column",
slug: "id",
id: "11d79abe",
type: "id",
......@@ -35,19 +35,23 @@ describe("issue 22197", () => {
it("should not reload cards in a dashboard when corresponding filters are not changed (metabase#22197)", () => {
createQuestionsAndDashboard().then(
({ dashboard_id, card1_id, card2_id }) => {
setFilterMapping({ dashboard_id, card1_id, card2_id });
interceptRequests({ dashboard_id, card1_id, card2_id });
setFilterMapping({ dashboard_id, card1_id, card2_id }).then(() => {
cy.visit(`/dashboard/${dashboard_id}`);
cy.wait("@getDashboard");
cy.wait("@getCardQuery1");
cy.wait("@getCardQuery2");
cy.visit(`/dashboard/${dashboard_id}`);
cy.wait("@getDashboard");
cy.wait("@getCardQuery1");
cy.wait("@getCardQuery2");
// the filter is connected only to the first card
cy.findByPlaceholderText(filterDetails.name).type("1{enter}");
cy.wait("@getCardQuery1");
cy.get("@getCardQuery1.all").should("have.length", 2);
cy.get("@getCardQuery2.all").should("have.length", 1);
cy.findByText(filterDetails.name).click();
popover().within(() => {
// the filter is connected only to the first card
cy.get("input").type("1{enter}");
cy.findByText("Add filter").click();
});
cy.wait("@getCardQuery1");
cy.get("@getCardQuery1.all").should("have.length", 2);
cy.get("@getCardQuery2.all").should("have.length", 1);
});
},
);
});
......@@ -70,35 +74,37 @@ const createQuestionsAndDashboard = () => {
};
const setFilterMapping = ({ dashboard_id, card1_id, card2_id }) => {
cy.request("POST", `/api/dashboard/${dashboard_id}/cards`, {
cardId: card1_id,
row: 0,
col: 0,
sizeX: 4,
sizeY: 4,
parameter_mappings: [
{
parameter_id: filterDetails.id,
card_id: card1_id,
target: ["dimension", ["field", PRODUCTS.ID, null]],
},
],
});
cy.request("POST", `/api/dashboard/${dashboard_id}/cards`, {
cardId: card2_id,
row: 0,
col: 4,
sizeX: 4,
sizeY: 4,
parameter_mappings: [
{
parameter_id: filterDetails.id,
card_id: card1_id,
target: ["dimension", ["field", ORDERS.ID, null]],
},
],
});
return cy
.request("POST", `/api/dashboard/${dashboard_id}/cards`, {
cardId: card1_id,
row: 0,
col: 0,
sizeX: 4,
sizeY: 4,
parameter_mappings: [
{
parameter_id: filterDetails.id,
card_id: card1_id,
target: ["dimension", ["field", PRODUCTS.ID, null]],
},
],
})
.then(() => {
return cy.request("POST", `/api/dashboard/${dashboard_id}/cards`, {
cardId: card2_id,
row: 0,
col: 4,
sizeX: 4,
sizeY: 4,
parameter_mappings: [
{
parameter_id: filterDetails.id,
card_id: card1_id,
target: ["dimension", ["field", ORDERS.ID, null]],
},
],
});
});
};
const interceptRequests = ({ dashboard_id, card1_id, card2_id }) => {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment