From defd866e61cdd507cc288ae930f13323b669d160 Mon Sep 17 00:00:00 2001 From: Ariya Hidayat <ariya@metabase.com> Date: Thu, 23 Jun 2022 16:26:03 -0700 Subject: [PATCH] Native query: construct `parameters[]` when necessary (#23210) --- frontend/src/metabase-lib/lib/Question.ts | 4 +++ .../query_builder/actions/core/core.js | 13 +++++++ .../metabase/query_builder/actions/native.js | 29 +++------------ .../template_tags/TagEditorSidebar.jsx | 1 + .../scenarios/native/native.cy.spec.js | 36 +++++++++++++++++++ 5 files changed, 59 insertions(+), 24 deletions(-) diff --git a/frontend/src/metabase-lib/lib/Question.ts b/frontend/src/metabase-lib/lib/Question.ts index e5125faa1c3..2e992561b95 100644 --- a/frontend/src/metabase-lib/lib/Question.ts +++ b/frontend/src/metabase-lib/lib/Question.ts @@ -1355,6 +1355,10 @@ export default class Question extends memoizeClass<QuestionInner>( dataset_query, }; + if (type === "native") { + card = assocIn(card, ["parameters"], []); + } + if (tableId != null) { card = assocIn(card, ["dataset_query", "query", "source-table"], tableId); } diff --git a/frontend/src/metabase/query_builder/actions/core/core.js b/frontend/src/metabase/query_builder/actions/core/core.js index 704e348cbbb..eb8d157ae1d 100644 --- a/frontend/src/metabase/query_builder/actions/core/core.js +++ b/frontend/src/metabase/query_builder/actions/core/core.js @@ -26,6 +26,11 @@ import { fetchAlertsForQuestion } from "metabase/alert/alert"; import Question from "metabase-lib/lib/Question"; import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; +import { + getTemplateTagsForParameters, + getTemplateTagParameters, +} from "metabase/parameters/utils/cards"; + import { trackNewQuestionSaved } from "../../analytics"; import { getCard, @@ -307,6 +312,14 @@ export const updateQuestion = ( ); } + const newDatasetQuery = newQuestion.query().datasetQuery(); + // Sync card's parameters with the template tags; + if (newDatasetQuery.type === "native") { + const templateTags = getTemplateTagsForParameters(newQuestion.card()); + const parameters = getTemplateTagParameters(templateTags); + newQuestion = newQuestion.setParameters(parameters); + } + // Replace the current question with a new one await dispatch.action(UPDATE_QUESTION, { card: newQuestion.card() }); diff --git a/frontend/src/metabase/query_builder/actions/native.js b/frontend/src/metabase/query_builder/actions/native.js index 7933655c22e..57b3622c6f0 100644 --- a/frontend/src/metabase/query_builder/actions/native.js +++ b/frontend/src/metabase/query_builder/actions/native.js @@ -20,7 +20,6 @@ import { SET_UI_CONTROLS } from "./ui"; import { getTemplateTagsForParameters, getTemplateTagParameters, - getTemplateTagParameter, } from "metabase/parameters/utils/cards"; export const TOGGLE_DATA_REFERENCE = "metabase/qb/TOGGLE_DATA_REFERENCE"; @@ -144,29 +143,11 @@ export const setTemplateTag = createThunkAction( }, ); - const { parameters } = updatedTagsCard; - if (parameters && Array.isArray(parameters)) { - if (parameters.length === 0) { - // reconstruct from the existing template tags - const tags = getTemplateTagsForParameters(updatedTagsCard); - const newParameters = getTemplateTagParameters(tags); - return assoc(updatedTagsCard, "parameters", newParameters); - } else { - // update an existing parameter - const index = parameters.findIndex(p => p.id === templateTag.id); - if (index < 0) { - console.warn(`Can't find parameter with id=${templateTag.id}!`); - } else { - parameters[index] = getTemplateTagParameter(templateTag); - } - } - } else { - const tags = getTemplateTagsForParameters(updatedTagsCard); - const newParameters = getTemplateTagParameters(tags); - return assoc(updatedTagsCard, "parameters", newParameters); - } - - return updatedTagsCard; + return assoc( + updatedTagsCard, + "parameters", + getTemplateTagParameters(getTemplateTagsForParameters(updatedTagsCard)), + ); }; }, ); diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx index 2d22ece6958..7ea25645969 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorSidebar.jsx @@ -50,6 +50,7 @@ export default class TagEditorSidebar extends React.Component { setParameterValue, onClose, } = this.props; + // The tag editor sidebar excludes snippets since they have a separate sidebar. const tags = query.templateTagsWithoutSnippets(); const database = query.database(); diff --git a/frontend/test/metabase/scenarios/native/native.cy.spec.js b/frontend/test/metabase/scenarios/native/native.cy.spec.js index 28bc2fe8502..178d34ce886 100644 --- a/frontend/test/metabase/scenarios/native/native.cy.spec.js +++ b/frontend/test/metabase/scenarios/native/native.cy.spec.js @@ -216,6 +216,42 @@ describe("scenarios > question > native", () => { cy.get("@sidebar").contains(/added/i); }); + it("should recognize template tags and save them as parameters", () => { + openNativeEditor().type( + "select * from PRODUCTS where CATEGORY={{cat}} and RATING >= {{stars}}", + { + parseSpecialCharSequences: false, + }, + ); + cy.get("input[placeholder*='Cat']").type("Gizmo"); + cy.get("input[placeholder*='Stars']").type("3"); + + cy.get(".NativeQueryEditor .Icon-play").click(); + cy.wait("@dataset"); + + cy.contains("Save").click(); + + modal().within(() => { + cy.findByLabelText("Name").type("SQL Products"); + cy.findByText("Save").click(); + + // parameters[] should reflect the template tags + cy.wait("@card").should(xhr => { + const requestBody = xhr.request?.body; + expect(requestBody?.parameters?.length).to.equal(2); + }); + }); + cy.findByText("Not now").click(); + + // Now load the question again and parameters[] should still be there + cy.intercept("GET", "/api/card/4").as("cardQuestion"); + cy.visit("/question/4?cat=Gizmo&stars=3"); + cy.wait("@cardQuestion").should(xhr => { + const responseBody = xhr.response?.body; + expect(responseBody?.parameters?.length).to.equal(2); + }); + }); + it("should link correctly from the variables sidebar (metabase#16212)", () => { cy.createNativeQuestion({ name: "test-question", -- GitLab