diff --git a/e2e/support/commands/api/dashboard.js b/e2e/support/commands/api/dashboard.js index edd17aa899bfd43471320677ea2259fbf93553f2..8cc6fe6b70d0077d584afd3a2d254edbc7f73f56 100644 --- a/e2e/support/commands/api/dashboard.js +++ b/e2e/support/commands/api/dashboard.js @@ -3,9 +3,10 @@ Cypress.Commands.add( ( { name = "Test Dashboard", + auto_apply_filters, enable_embedding, embedding_params, - auto_apply_filters, + dashcards, ...dashboardDetails } = {}, { wrapId = false, idAlias = "dashboardId" } = {}, @@ -18,11 +19,16 @@ Cypress.Commands.add( if (wrapId) { cy.wrap(body.id).as(idAlias); } - if (enable_embedding != null || auto_apply_filters != null) { + if ( + enable_embedding != null || + auto_apply_filters != null || + Array.isArray(dashcards) + ) { cy.request("PUT", `/api/dashboard/${body.id}`, { + auto_apply_filters, enable_embedding, embedding_params, - auto_apply_filters, + dashcards, }); } }, diff --git a/e2e/test/scenarios/dashboard-cards/click-behavior.cy.spec.js b/e2e/test/scenarios/dashboard-cards/click-behavior.cy.spec.js index b630a6059c5f5d6005715ef7c50f5938165f9bc4..ba99cc7035a46ec85084daec2009c285dc4b02c7 100644 --- a/e2e/test/scenarios/dashboard-cards/click-behavior.cy.spec.js +++ b/e2e/test/scenarios/dashboard-cards/click-behavior.cy.spec.js @@ -1,5 +1,6 @@ import { USER_GROUPS } from "e2e/support/cypress_data"; import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; +import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data"; import { addOrUpdateDashboardCard, dashboardHeader, @@ -19,7 +20,10 @@ import { visitEmbeddedPage, } from "e2e/support/helpers"; -import { createMockActionParameter } from "metabase-types/api/mocks"; +import { + createMockActionParameter, + createMockDashboardCard, +} from "metabase-types/api/mocks"; import { b64hash_to_utf8 } from "metabase/lib/encoding"; const COUNT_COLUMN_ID = "count"; @@ -52,7 +56,7 @@ const FIRST_TAB = { id: 900, name: "first" }; const SECOND_TAB = { id: 901, name: "second" }; const THIRD_TAB = { id: 902, name: "third" }; -const { ORDERS_ID, ORDERS } = SAMPLE_DATABASE; +const { ORDERS_ID, ORDERS, PEOPLE } = SAMPLE_DATABASE; const TARGET_DASHBOARD = { name: "Target dashboard", @@ -109,6 +113,14 @@ const DASHBOARD_FILTER_TIME = createMockActionParameter({ sectionId: "date", }); +const DASHBOARD_FILTER_NUMBER = createMockActionParameter({ + id: "3", + name: "Number filter", + slug: "filter-number", + type: "number/>=", + sectionId: "number", +}); + const QUERY_FILTER_CREATED_AT = [ "between", [ @@ -263,7 +275,19 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { wrapId: true, idAlias: "targetDashboardId", }, - ); + ).then(dashboardId => { + cy.request("PUT", `/api/dashboard/${dashboardId}`, { + dashcards: [ + createMockDashboardCard({ + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + createTextFilterMapping({ card_id: ORDERS_QUESTION_ID }), + ], + }), + ], + }); + }); + cy.createQuestionAndDashboard({ questionDetails }).then( ({ body: card }) => { visitDashboard(card.dashboard_id); @@ -305,7 +329,20 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { wrapId: true, idAlias: "targetDashboardId", }, - ); + ).then(dashboardId => { + cy.request("PUT", `/api/dashboard/${dashboardId}`, { + dashcards: [ + createMockDashboardCard({ + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + createTextFilterMapping({ card_id: ORDERS_QUESTION_ID }), + createTimeFilterMapping({ card_id: ORDERS_QUESTION_ID }), + ], + }), + ], + }); + }); + cy.createQuestionAndDashboard({ questionDetails }).then( ({ body: card }) => { visitDashboard(card.dashboard_id); @@ -352,7 +389,20 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { idAlias: "targetDashboardId", }; - createDashboardWithTabs({ dashboard, tabs, options }); + createDashboardWithTabs({ + dashboard, + tabs, + dashcards: [ + createMockDashboardCard({ + dashboard_tab_id: SECOND_TAB.id, + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + createTextFilterMapping({ card_id: ORDERS_QUESTION_ID }), + ], + }), + ], + options, + }); const TAB_SLUG_MAP = {}; @@ -787,8 +837,17 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { }; cy.createQuestionAndDashboard({ questionDetails, dashboardDetails }).then( - ({ body: card }) => { - visitDashboard(card.dashboard_id); + ({ body: dashcard }) => { + addOrUpdateDashboardCard({ + dashboard_id: dashcard.dashboard_id, + card_id: dashcard.card_id, + card: { + parameter_mappings: [ + createTextFilterMapping({ card_id: dashcard.card_id }), + ], + }, + }); + visitDashboard(dashcard.dashboard_id); }, ); @@ -815,7 +874,7 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { cy.button(DASHBOARD_FILTER_TEXT.name).click(); popover().within(() => { - cy.findByPlaceholderText("Enter some text").type(FILTER_VALUE); + cy.findByPlaceholderText("Search by Name").type("Dell Adams"); cy.button("Add filter").click(); }); @@ -845,12 +904,21 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { it("allows updating single dashboard filter and changing it back to default click behavior", () => { const dashboardDetails = { - parameters: [DASHBOARD_FILTER_TEXT], + parameters: [DASHBOARD_FILTER_NUMBER], }; cy.createQuestionAndDashboard({ questionDetails, dashboardDetails }).then( - ({ body: card }) => { - visitDashboard(card.dashboard_id); + ({ body: dashcard }) => { + addOrUpdateDashboardCard({ + dashboard_id: dashcard.dashboard_id, + card_id: dashcard.card_id, + card: { + parameter_mappings: [ + createNumberFilterMapping({ card_id: dashcard.card_id }), + ], + }, + }); + visitDashboard(dashcard.dashboard_id); cy.location().then(({ pathname }) => { cy.wrap(pathname).as("originalPathname"); }); @@ -861,7 +929,7 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { getDashboardCard().realHover().icon("click").click(); cy.get("aside").findByText("Update a dashboard filter").click(); - addTextParameter(); + addNumericParameter(); cy.get("aside").button("Done").click(); saveDashboard({ waitMs: 250 }); @@ -874,7 +942,7 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { cy.location().should(({ pathname, search }) => { expect(pathname).to.equal(originalPathname); expect(search).to.equal( - `?${DASHBOARD_FILTER_TEXT.slug}=${POINT_COUNT}`, + `?${DASHBOARD_FILTER_NUMBER.slug}=${POINT_COUNT}`, ); }); }); @@ -888,8 +956,18 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { }; cy.createQuestionAndDashboard({ questionDetails, dashboardDetails }).then( - ({ body: card }) => { - visitDashboard(card.dashboard_id); + ({ body: dashcard }) => { + addOrUpdateDashboardCard({ + dashboard_id: dashcard.dashboard_id, + card_id: dashcard.card_id, + card: { + parameter_mappings: [ + createTextFilterMapping({ card_id: dashcard.card_id }), + createTimeFilterMapping({ card_id: dashcard.card_id }), + ], + }, + }); + visitDashboard(dashcard.dashboard_id); cy.location().then(({ pathname }) => { cy.wrap(pathname).as("originalPathname"); }); @@ -944,8 +1022,18 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { }; cy.createQuestionAndDashboard({ questionDetails, dashboardDetails }).then( - ({ body: card }) => { - visitDashboard(card.dashboard_id); + ({ body: dashcard }) => { + addOrUpdateDashboardCard({ + dashboard_id: dashcard.dashboard_id, + card_id: dashcard.card_id, + card: { + parameter_mappings: [ + createTextFilterMapping({ card_id: dashcard.card_id }), + createTimeFilterMapping({ card_id: dashcard.card_id }), + ], + }, + }); + visitDashboard(dashcard.dashboard_id); cy.location().then(({ pathname }) => { cy.wrap(pathname).as("originalPathname"); }); @@ -1011,6 +1099,15 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { { ...TARGET_DASHBOARD, parameters: [DASHBOARD_FILTER_TEXT, DASHBOARD_FILTER_TIME], + dashcards: [ + createMockDashboardCard({ + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + createTextFilterMapping({ card_id: ORDERS_QUESTION_ID }), + createTimeFilterMapping({ card_id: ORDERS_QUESTION_ID }), + ], + }), + ], }, { wrapId: true, @@ -1140,7 +1237,21 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { idAlias: "targetDashboardId", }; - createDashboardWithTabs({ dashboard, tabs, options }); + createDashboardWithTabs({ + dashboard, + tabs, + dashcards: [ + createMockDashboardCard({ + dashboard_tab_id: SECOND_TAB.id, + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + createTextFilterMapping({ card_id: ORDERS_QUESTION_ID }), + createTimeFilterMapping({ card_id: ORDERS_QUESTION_ID }), + ], + }), + ], + options, + }); const TAB_SLUG_MAP = {}; tabs.forEach(tab => { @@ -1202,6 +1313,15 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { { ...TARGET_DASHBOARD, parameters: [DASHBOARD_FILTER_TEXT, DASHBOARD_FILTER_TIME], + dashcards: [ + createMockDashboardCard({ + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + createTextFilterMapping({ card_id: ORDERS_QUESTION_ID }), + createTimeFilterMapping({ card_id: ORDERS_QUESTION_ID }), + ], + }), + ], }, { wrapId: true, @@ -1209,8 +1329,17 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { }, ); cy.createQuestionAndDashboard({ questionDetails, dashboardDetails }).then( - ({ body: card }) => { - visitDashboard(card.dashboard_id); + ({ body: dashcard }) => { + addOrUpdateDashboardCard({ + dashboard_id: dashcard.dashboard_id, + card_id: dashcard.card_id, + card: { + parameter_mappings: [ + createTextFilterMapping({ card_id: dashcard.card_id }), + ], + }, + }); + visitDashboard(dashcard.dashboard_id); cy.location().then(({ pathname }) => { cy.wrap(pathname).as("originalPathname"); }); @@ -1292,7 +1421,7 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { cy.button(DASHBOARD_FILTER_TEXT.name).click(); popover().within(() => { cy.icon("close").click(); - cy.findByPlaceholderText("Enter some text").type(FILTER_VALUE); + cy.findByPlaceholderText("Search by Name").type("Dell Adams"); cy.button("Update filter").click(); }); onNextAnchorClick(anchor => { @@ -1301,7 +1430,7 @@ describe("scenarios > dashboard > dashboard cards > click behavior", () => { expect(anchor).to.have.attr("target", "_blank"); }); getTableCell(COLUMN_INDEX.CREATED_AT) - .should("have.text", `Created at: ${POINT_CREATED_AT_FORMATTED}`) + .should("have.text", `Created at: November 2023`) .click(); })(); }); @@ -1674,6 +1803,55 @@ const addTimeParameter = () => { }); }; +const addNumericParameter = () => { + cy.get("aside").findByText(DASHBOARD_FILTER_NUMBER.name).click(); + popover().within(() => { + cy.findByText(CREATED_AT_COLUMN_NAME).should("exist"); + cy.findByText(COUNT_COLUMN_NAME).should("exist").click(); + }); +}; + +const createTextFilterMapping = ({ card_id }) => { + const fieldRef = [ + "field", + PEOPLE.NAME, + { + "base-type": "type/Text", + "source-field": ORDERS.USER_ID, + }, + ]; + + return { + card_id, + parameter_id: DASHBOARD_FILTER_TEXT.id, + target: ["dimension", fieldRef], + }; +}; + +const createTimeFilterMapping = ({ card_id }) => { + const fieldRef = [ + "field", + ORDERS.CREATED_AT, + { "base-type": "type/DateTime" }, + ]; + + return { + card_id, + parameter_id: DASHBOARD_FILTER_TIME.id, + target: ["dimension", fieldRef], + }; +}; + +const createNumberFilterMapping = ({ card_id }) => { + const fieldRef = ["field", ORDERS.QUANTITY, { "base-type": "type/Number" }]; + + return { + card_id, + parameter_id: DASHBOARD_FILTER_NUMBER.id, + target: ["dimension", fieldRef], + }; +}; + const assertDrillThroughMenuOpen = () => { popover() .should("contain", "See these Orders") @@ -1730,21 +1908,24 @@ const getCountToDashboardFilterMapping = () => { return cy.get("aside").contains(`${COUNT_COLUMN_NAME} updates 1 filter`); }; -const createDashboardWithTabs = ({ dashboard, tabs, options }) => { - cy.createDashboard(dashboard, options); - - cy.get(`@${options.idAlias}`) - .then(dashboardId => { - cy.request("PUT", `/api/dashboard/${dashboardId}/cards`, { - cards: [], - tabs, - }); - }) - .then(({ body }) => { - // wrap tabs - - body.tabs.forEach(tab => { +const createDashboardWithTabs = ({ + dashboard: dashboardDetails, + tabs, + dashcards = [], + options, +}) => { + cy.createDashboard(dashboardDetails).then(({ body: dashboard }) => { + if (options.wrapId) { + cy.wrap(dashboard.id).as(options.idAlias ?? "dashboardId"); + } + cy.request("PUT", `/api/dashboard/${dashboard.id}`, { + ...dashboard, + dashcards, + tabs, + }).then(({ body: dashboard }) => { + dashboard.tabs.forEach(tab => { cy.wrap(tab.id).as(`${tab.name}-id`); }); }); + }); }; diff --git a/e2e/test/scenarios/dashboard-filters/dashboard-filters-number.cy.spec.js b/e2e/test/scenarios/dashboard-filters/dashboard-filters-number.cy.spec.js index 778bbc39dcfd5f7593d630a617507a1ac834b9ea..7b41969de4afba1947943beea447be223612a142 100644 --- a/e2e/test/scenarios/dashboard-filters/dashboard-filters-number.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/dashboard-filters-number.cy.spec.js @@ -7,6 +7,7 @@ import { saveDashboard, setFilter, visitDashboard, + selectDashboardFilter, } from "e2e/support/helpers"; import { ORDERS_DASHBOARD_ID, @@ -57,6 +58,7 @@ describe("scenarios > dashboard > filters > number", () => { it(`should work when set as the default filter`, () => { setFilter("Number", "Equal to"); + selectDashboardFilter(cy.findByTestId("dashcard"), "Tax"); // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage cy.findByText("Default value").next().click(); diff --git a/e2e/test/scenarios/dashboard-filters/dashboard-filters-source.cy.spec.js b/e2e/test/scenarios/dashboard-filters/dashboard-filters-source.cy.spec.js index d43605185263d9f213bb3c93841b3737e0e432d3..a9532d5a2ac5793a1b506a20a5359266420e573b 100644 --- a/e2e/test/scenarios/dashboard-filters/dashboard-filters-source.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/dashboard-filters-source.cy.spec.js @@ -79,21 +79,6 @@ describe("scenarios > dashboard > filters", { tags: "@slow" }, () => { archiveQuestion(); }); - it("should be able to use a structured question source without mapping to a field", () => { - cy.createQuestion(structuredSourceQuestion); - cy.createQuestionAndDashboard({ - questionDetails: targetQuestion, - }).then(({ body: { dashboard_id } }) => { - visitDashboard(dashboard_id); - }); - - editDashboard(); - setFilter("Text or Category", "Is"); - setFilterQuestionSource({ question: "GUI source", field: "Category" }); - saveDashboard(); - filterDashboard(); - }); - it("should be able to use a structured question source when embedded", () => { cy.createQuestion(structuredSourceQuestion).then( ({ body: { id: questionId } }) => { diff --git a/e2e/test/scenarios/dashboard-filters/reproductions/16663-filters-can-stay-in-url.cy.spec.js b/e2e/test/scenarios/dashboard-filters/reproductions/16663-filters-can-stay-in-url.cy.spec.js index 5de79913775dd315fe0d472e2bc7c782e05f4757..6b977a6dc8011c519143a3b29071fe3845cd8061 100644 --- a/e2e/test/scenarios/dashboard-filters/reproductions/16663-filters-can-stay-in-url.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/reproductions/16663-filters-can-stay-in-url.cy.spec.js @@ -1,8 +1,12 @@ -import { restore, visitDashboard } from "e2e/support/helpers"; +import { + addOrUpdateDashboardCard, + restore, + visitDashboard, +} from "e2e/support/helpers"; import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; -const { ORDERS_ID } = SAMPLE_DATABASE; +const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE; const questionDetails = { query: { @@ -10,18 +14,16 @@ const questionDetails = { }, }; -const parameters = [ - { - name: "Quarter and Year", - slug: "quarter_and_year", - id: "f8ae0c97", - type: "date/quarter-year", - sectionId: "date", - default: "Q1-2023", - }, -]; +const FILTER = { + name: "Quarter and Year", + slug: "quarter_and_year", + id: "f8ae0c97", + type: "date/quarter-year", + sectionId: "date", + default: "Q1-2023", +}; -const dashboardDetails = { parameters }; +const dashboardDetails = { parameters: [FILTER] }; describe("issue 16663", () => { beforeEach(() => { @@ -37,6 +39,28 @@ describe("issue 16663", () => { ({ body: dashboardCard }) => { const { dashboard_id } = dashboardCard; + addOrUpdateDashboardCard({ + dashboard_id: dashboardCard.dashboard_id, + card_id: dashboardCard.card_id, + card: { + parameter_mappings: [ + { + parameter_id: FILTER.id, + card_id: dashboardCard.card_id, + target: [ + "dimension", + [ + "field", + ORDERS.CREATED_AT, + { + "base-type": "type/DateTime", + }, + ], + ], + }, + ], + }, + }); visitDashboard(dashboard_id); }, ); diff --git a/e2e/test/scenarios/dashboard-filters/reproductions/26230-dashboard-sticky-filter-incorrectly-positioned.cy.spec.js b/e2e/test/scenarios/dashboard-filters/reproductions/26230-dashboard-sticky-filter-incorrectly-positioned.cy.spec.js index 8d32c252b06a538cb0918e1e005dbd6c8a2f81b3..1a55166b152d5edf0d4af70ca1ee9dba95a85d9c 100644 --- a/e2e/test/scenarios/dashboard-filters/reproductions/26230-dashboard-sticky-filter-incorrectly-positioned.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/reproductions/26230-dashboard-sticky-filter-incorrectly-positioned.cy.spec.js @@ -1,9 +1,9 @@ -import { - addOrUpdateDashboardCard, - getTextCardDetails, - restore, - visitDashboard, -} from "e2e/support/helpers"; +import { restore, visitDashboard } from "e2e/support/helpers"; +import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; +import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data"; +import { createMockDashboardCard } from "metabase-types/api/mocks"; + +const { ORDERS, PEOPLE } = SAMPLE_DATABASE; describe("issue 26230", () => { beforeEach(() => { @@ -33,36 +33,36 @@ describe("issue 26230", () => { }); }); +const FILTER_1 = { + id: "12345678", + name: "Text", + slug: "text", + type: "string/=", + sectionId: "string", +}; + +const FILTER_2 = { + id: "87654321", + name: "Text", + slug: "text", + type: "string/=", + sectionId: "string", +}; + function prepareAndVisitDashboards() { cy.createDashboard({ name: "dashboard with a tall card", - parameters: [ - { - id: "12345678", - name: "Text", - slug: "text", - type: "string/=", - sectionId: "string", - }, - ], + parameters: [FILTER_1], }).then(({ body: { id } }) => { - createTextDashcard(id); + createDashCard(id, FILTER_1); bookmarkDashboard(id); }); cy.createDashboard({ name: "dashboard with a tall card 2", - parameters: [ - { - id: "87654321", - name: "Text", - slug: "text", - type: "string/=", - sectionId: "string", - }, - ], + parameters: [FILTER_2], }).then(({ body: { id } }) => { - createTextDashcard(id); + createDashCard(id, FILTER_2); bookmarkDashboard(id); visitDashboard(id); }); @@ -72,14 +72,30 @@ function bookmarkDashboard(dashboardId) { cy.request("POST", `/api/bookmark/dashboard/${dashboardId}`); } -function createTextDashcard(id) { - addOrUpdateDashboardCard({ - dashboard_id: id, - card_id: null, - card: getTextCardDetails({ - size_x: 5, - size_y: 20, - text: "I am a tall card", - }), +function createDashCard(dashboardId, mappedFilter) { + cy.request("PUT", `/api/dashboard/${dashboardId}`, { + dashcards: [ + createMockDashboardCard({ + id: -dashboardId, + dashboard_id: dashboardId, + size_x: 5, + size_y: 20, + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + { + parameter_id: mappedFilter.id, + card_id: ORDERS_QUESTION_ID, + target: [ + "dimension", + [ + "field", + PEOPLE.NAME, + { "base-type": "type/Text", "source-field": ORDERS.USER_ID }, + ], + ], + }, + ], + }), + ], }); } diff --git a/e2e/test/scenarios/dashboard-filters/reproductions/31662-between-filter-default-value.cy.spec.js b/e2e/test/scenarios/dashboard-filters/reproductions/31662-between-filter-default-value.cy.spec.js index f9201162af6f993e5984fe2b93b6ffc4decf79fa..68d31baf7cb92009cfd345605ce629927386481c 100644 --- a/e2e/test/scenarios/dashboard-filters/reproductions/31662-between-filter-default-value.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/reproductions/31662-between-filter-default-value.cy.spec.js @@ -1,4 +1,4 @@ -import { filterWidget, popover, restore } from "e2e/support/helpers"; +import { editDashboard, popover, restore, sidebar } from "e2e/support/helpers"; const parameterDetails = { name: "Between", @@ -29,10 +29,14 @@ describe("issue 31662", () => { }, ); cy.findByTestId("dashboard-empty-state").should("be.visible"); - filterWidget().findByText("2 selections").click(); + editDashboard(); + cy.findByTestId("edit-dashboard-parameters-widget-container") + .findByText("Between") + .click(); + sidebar().findByText("2 selections").click(); popover().within(() => { - cy.findByDisplayValue("10").should("be.visible"); - cy.findByDisplayValue("20").should("be.visible"); + cy.findByDisplayValue("3").should("be.visible"); + cy.findByDisplayValue("5").should("be.visible"); }); }); }); diff --git a/e2e/test/scenarios/dashboard-filters/reproductions/8030-32444-reload-card-without-change.cy.spec.js b/e2e/test/scenarios/dashboard-filters/reproductions/8030-32444-reload-card-without-change.cy.spec.js index a3f6d832f1885327c68b80bde91440e235a79697..b5e5d092183de1214a8ecfabdc3d994d9f56b996 100644 --- a/e2e/test/scenarios/dashboard-filters/reproductions/8030-32444-reload-card-without-change.cy.spec.js +++ b/e2e/test/scenarios/dashboard-filters/reproductions/8030-32444-reload-card-without-change.cy.spec.js @@ -7,6 +7,7 @@ import { editDashboard, setFilter, saveDashboard, + selectDashboardFilter, } from "e2e/support/helpers"; import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; @@ -84,8 +85,7 @@ describe("issue 32444", () => { it("should not reload dashboard cards not connected to a filter (metabase#32444)", () => { cy.createDashboardWithQuestions({ - dashboardName: "Dashboard with a card and filter", - questions: [questionWithFilter], + questions: [question1Details, questionWithFilter], }).then(({ dashboard }) => { cy.intercept( "POST", @@ -95,15 +95,24 @@ describe("issue 32444", () => { visitDashboard(dashboard.id); editDashboard(dashboard.id); + cy.get("@getCardQuery.all").should("have.length", 2); + setFilter("Text or Category", "Is"); + selectDashboardFilter(cy.findAllByTestId("dashcard").first(), "Title"); + cy.findAllByTestId("dashcard") + .eq(1) + .findByLabelText("Disconnect") + .click(); + saveDashboard(); cy.wait("@getCardQuery"); - cy.get("@getCardQuery.all").should("have.length", 1); + cy.get("@getCardQuery.all").should("have.length", 4); - addFilterValue(5); + addFilterValue("Aerodynamic Bronze Hat"); - cy.get("@getCardQuery.all").should("have.length", 1); + cy.wait("@getCardQuery"); + cy.get("@getCardQuery.all").should("have.length", 5); }); }); }); @@ -174,6 +183,6 @@ const interceptRequests = ({ dashboard_id, card1_id, card2_id }) => { function addFilterValue(value) { filterWidget().click(); - cy.findByPlaceholderText("Enter some text").type(`${value}{enter}`); + cy.findByText(value).click(); cy.button("Add filter").click(); } diff --git a/e2e/test/scenarios/dashboard/tabs.cy.spec.js b/e2e/test/scenarios/dashboard/tabs.cy.spec.js index 12d53db14344b5ec736cdd9865b9d4b9c8b0c3fe..6f1c5b146e401c298997cc87e97d284cc3814d0f 100644 --- a/e2e/test/scenarios/dashboard/tabs.cy.spec.js +++ b/e2e/test/scenarios/dashboard/tabs.cy.spec.js @@ -32,14 +32,57 @@ import { popover, } from "e2e/support/helpers"; +import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; import { ORDERS_DASHBOARD_ID, ORDERS_DASHBOARD_DASHCARD_ID, ORDERS_QUESTION_ID, ORDERS_COUNT_QUESTION_ID, + ORDERS_BY_YEAR_QUESTION_ID, ADMIN_PERSONAL_COLLECTION_ID, NORMAL_PERSONAL_COLLECTION_ID, } from "e2e/support/cypress_sample_instance_data"; +import { createMockDashboardCard } from "metabase-types/api/mocks"; + +const { ORDERS, PEOPLE } = SAMPLE_DATABASE; + +const DASHBOARD_DATE_FILTER = { + id: "1", + name: "Date filter", + slug: "filter-date", + type: "date/month-year", +}; + +const DASHBOARD_NUMBER_FILTER = { + id: "2", + name: "Number filter", + slug: "filter-number", + type: "number/=", +}; + +const DASHBOARD_TEXT_FILTER = { + id: "3", + name: "Text filter", + slug: "filter-text", + type: "string/contains", +}; + +const DASHBOARD_LOCATION_FILTER = { + id: "4", + name: "Location filter", + slug: "filter-location", + type: "string/=", +}; + +const TAB_1 = { + id: 1, + name: "Tab 1", +}; + +const TAB_2 = { + id: 2, + name: "Tab 2", +}; describe("scenarios > dashboard > tabs", () => { beforeEach(() => { @@ -77,6 +120,64 @@ describe("scenarios > dashboard > tabs", () => { }); }); + it("should only display filters mapped to cards on the selected tab", () => { + createDashboardWithTabs({ + tabs: [TAB_1, TAB_2], + parameters: [ + DASHBOARD_DATE_FILTER, + { ...DASHBOARD_NUMBER_FILTER, default: 20 }, + { ...DASHBOARD_TEXT_FILTER, default: "fa" }, + DASHBOARD_LOCATION_FILTER, + ], + dashcards: [ + createMockDashboardCard({ + id: -1, + card_id: ORDERS_QUESTION_ID, + dashboard_tab_id: TAB_1.id, + parameter_mappings: [ + createDateFilterMapping({ card_id: ORDERS_QUESTION_ID }), + createTextFilterMapping({ card_id: ORDERS_BY_YEAR_QUESTION_ID }), + ], + }), + createMockDashboardCard({ + id: -2, + card_id: ORDERS_BY_YEAR_QUESTION_ID, + dashboard_tab_id: TAB_2.id, + parameter_mappings: [ + createDateFilterMapping({ card_id: ORDERS_BY_YEAR_QUESTION_ID }), + createNumberFilterMapping({ card_id: ORDERS_BY_YEAR_QUESTION_ID }), + ], + }), + ], + }).then(dashboard => visitDashboard(dashboard.id)); + + assertFiltersVisibility({ + visible: [DASHBOARD_DATE_FILTER, DASHBOARD_TEXT_FILTER], + hidden: [DASHBOARD_NUMBER_FILTER, DASHBOARD_LOCATION_FILTER], + }); + + assertFilterValues([ + [DASHBOARD_DATE_FILTER, undefined], + [DASHBOARD_TEXT_FILTER, "fa"], + [DASHBOARD_NUMBER_FILTER, 20], + [DASHBOARD_LOCATION_FILTER, undefined], + ]); + + goToTab(TAB_2.name); + + assertFiltersVisibility({ + visible: [DASHBOARD_DATE_FILTER, DASHBOARD_NUMBER_FILTER], + hidden: [DASHBOARD_TEXT_FILTER, DASHBOARD_LOCATION_FILTER], + }); + + assertFilterValues([ + [DASHBOARD_DATE_FILTER, undefined], + [DASHBOARD_TEXT_FILTER, "fa"], + [DASHBOARD_NUMBER_FILTER, 20], + [DASHBOARD_LOCATION_FILTER, undefined], + ]); + }); + it("should allow undoing a tab deletion", () => { visitDashboardAndCreateTab({ dashboardId: ORDERS_DASHBOARD_ID, @@ -544,3 +645,79 @@ function delayResponse(delayMs) { }); }; } + +function createDashboardWithTabs({ dashcards, tabs, ...dashboardDetails }) { + return cy.createDashboard(dashboardDetails).then(({ body: dashboard }) => { + cy.request("PUT", `/api/dashboard/${dashboard.id}`, { + ...dashboard, + dashcards, + tabs, + }).then(({ body: dashboard }) => cy.wrap(dashboard)); + }); +} + +const createTextFilterMapping = ({ card_id }) => { + const fieldRef = [ + "field", + PEOPLE.NAME, + { + "base-type": "type/Text", + "source-field": ORDERS.USER_ID, + }, + ]; + + return { + card_id, + parameter_id: DASHBOARD_TEXT_FILTER.id, + target: ["dimension", fieldRef], + }; +}; + +const createDateFilterMapping = ({ card_id }) => { + const fieldRef = [ + "field", + ORDERS.CREATED_AT, + { "base-type": "type/DateTime" }, + ]; + + return { + card_id, + parameter_id: DASHBOARD_DATE_FILTER.id, + target: ["dimension", fieldRef], + }; +}; + +const createNumberFilterMapping = ({ card_id }) => { + const fieldRef = ["field", ORDERS.QUANTITY, { "base-type": "type/Number" }]; + + return { + card_id, + parameter_id: DASHBOARD_NUMBER_FILTER.id, + target: ["dimension", fieldRef], + }; +}; + +function assertFiltersVisibility({ visible = [], hidden = [] }) { + cy.findByTestId("dashboard-parameters-widget-container", () => { + visible.forEach(filter => cy.findByText(filter.name).should("exist")); + hidden.forEach(filter => cy.findByText(filter.name).should("not.exist")); + }); + + // Ensure all filters are visible in edit mode + editDashboard(); + cy.findByTestId("edit-dashboard-parameters-widget-container", () => { + [...visible, ...hidden].forEach(filter => + cy.findByText(filter.name).should("exist"), + ); + }); + + cy.findByTestId("edit-bar").button("Cancel").click(); +} + +function assertFilterValues(filterValues) { + filterValues.forEach(([filter, value]) => { + const displayValue = value === undefined ? "" : value.toString(); + const filterQueryParameter = `${filter.slug}=${displayValue}`; + cy.location("search").should("contain", filterQueryParameter); + }); +} diff --git a/e2e/test/scenarios/dashboard/text-dashcards.cy.spec.js b/e2e/test/scenarios/dashboard/text-dashcards.cy.spec.js index 1f83409f1dd202755c83ef7277f32316dfd18372..c492ea9355cdcf432f24d495cd134fab8925b241 100644 --- a/e2e/test/scenarios/dashboard/text-dashcards.cy.spec.js +++ b/e2e/test/scenarios/dashboard/text-dashcards.cy.spec.js @@ -6,6 +6,7 @@ import { addTextBox, editDashboard, saveDashboard, + selectDashboardFilter, describeWithSnowplow, enableTracking, resetSnowplow, @@ -146,6 +147,7 @@ describe("scenarios > dashboard > text and headings", () => { cy.findByText("Text or Category").click(); cy.findByText("Is").click(); }); + selectDashboardFilter(cy.findAllByTestId("dashcard").first(), "Name"); cy.findByTestId("edit-bar").findByText("Save").click(); // confirm text box and filter are still there diff --git a/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js b/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js index 535eb09bea0e1f4e85a3cfc0ee22fb7533bc83a6..e227285553bf60b9f9a78feeba9dd97d2a849083 100644 --- a/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js +++ b/e2e/test/scenarios/embedding/embedding-full-app.cy.spec.js @@ -9,11 +9,16 @@ import { getDashboardCard, getTextCardDetails, updateDashboardCards, + getNextUnsavedDashboardCardId, } from "e2e/support/helpers"; +import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; import { ORDERS_QUESTION_ID, ORDERS_DASHBOARD_ID, } from "e2e/support/cypress_sample_instance_data"; +import { createMockDashboardCard } from "metabase-types/api/mocks"; + +const { ORDERS } = SAMPLE_DATABASE; describeEE("scenarios > embedding > full app", () => { beforeEach(() => { @@ -342,11 +347,12 @@ describeEE("scenarios > embedding > full app", () => { }); it("should have parameters header occupied the entire horizontal space when visiting a dashboard via navigation (metabase#30645)", () => { + const filterId = "50c9eac6"; const dashboardDetails = { name: "interactive dashboard embedding", parameters: [ { - id: "50c9eac6", + id: filterId, name: "ID", slug: "id", type: "id", @@ -355,14 +361,33 @@ describeEE("scenarios > embedding > full app", () => { }; cy.createDashboard(dashboardDetails).then( ({ body: { id: dashboardId } }) => { - const card = getTextCardDetails({ + const textDashcard = getTextCardDetails({ col: 0, row: 0, size_x: 6, size_y: 20, text: "I am a very long text card", }); - updateDashboardCards({ dashboard_id: dashboardId, cards: [card] }); + const dashcard = createMockDashboardCard({ + id: getNextUnsavedDashboardCardId(), + col: 8, + row: 0, + card_id: ORDERS_QUESTION_ID, + parameter_mappings: [ + { + parameter_id: filterId, + card_id: ORDERS_QUESTION_ID, + target: [ + "dimension", + ["field", ORDERS.ID, { "base-type": "type/Integer" }], + ], + }, + ], + }); + updateDashboardCards({ + dashboard_id: dashboardId, + cards: [dashcard, textDashcard], + }); }, ); diff --git a/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx b/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx index 81a95e18f7dcf900bfe214dfc197805db4eba0b2..1302e6d9c18dce1fa9c6abd7c9d27169707e89ec 100644 --- a/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx +++ b/frontend/src/metabase/dashboard/components/Dashboard/Dashboard.tsx @@ -229,25 +229,41 @@ function DashboardInner(props: DashboardProps) { const previousTabId = usePrevious(selectedTabId); const previousParameterValues = usePrevious(parameterValues); - const visibleParameters = useMemo( - () => getVisibleParameters(parameters), - [parameters], - ); - - const tabHasCards = useMemo(() => { + const currentTabDashcards = useMemo(() => { if (!Array.isArray(dashboard?.dashcards)) { - return false; + return []; } if (!selectedTabId) { - return dashboard.dashcards.length > 0; + return dashboard.dashcards; } - const tabDashCards = dashboard.dashcards.filter( + return dashboard.dashcards.filter( dc => dc.dashboard_tab_id === selectedTabId, ); - return tabDashCards.length > 0; }, [dashboard, selectedTabId]); + const hiddenParameterSlugs = useMemo(() => { + if (isEditing) { + // All filters should be visible in edit mode + return undefined; + } + + const currentTabParameterIds = currentTabDashcards.flatMap( + dc => dc.parameter_mappings?.map(pm => pm.parameter_id) ?? [], + ); + const hiddenParameters = parameters.filter( + parameter => !currentTabParameterIds.includes(parameter.id), + ); + + return hiddenParameters.map(p => p.slug).join(","); + }, [parameters, currentTabDashcards, isEditing]); + + const visibleParameters = useMemo( + () => getVisibleParameters(parameters, hiddenParameterSlugs), + [parameters, hiddenParameterSlugs], + ); + const canWrite = Boolean(dashboard?.can_write); + const tabHasCards = currentTabDashcards.length > 0; const dashboardHasCards = dashboard?.dashcards.length > 0; const hasVisibleParameters = visibleParameters.length > 0; @@ -439,6 +455,7 @@ function DashboardInner(props: DashboardProps) { isAutoApplyFilters ? parameterValues : draftParameterValues, )} editingParameter={editingParameter} + hideParameters={hiddenParameterSlugs} dashboard={dashboard} isFullscreen={isFullscreen} isNightMode={shouldRenderAsNightMode}