From 4e8889337e963ad4574813843c0e54de2512aa3d Mon Sep 17 00:00:00 2001 From: Paul Rosenzweig <paulrosenzweig@users.noreply.github.com> Date: Thu, 26 Dec 2019 18:16:02 -0500 Subject: [PATCH] Preserve template-tags key order (#11582) --- .../src/metabase/query_builder/actions.js | 18 ++++---- .../components/NativeQueryEditor.jsx | 5 ++- .../components/NativeQueryEditor.cy.spec.js | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/frontend/src/metabase/query_builder/actions.js b/frontend/src/metabase/query_builder/actions.js index 66027a73f8d..55c2958540b 100644 --- a/frontend/src/metabase/query_builder/actions.js +++ b/frontend/src/metabase/query_builder/actions.js @@ -629,15 +629,19 @@ export const updateTemplateTag = createThunkAction( delete updatedCard.description; } - // using updateIn instead of assocIn due to not preserving order of keys + // we need to preserve the order of the keys to avoid UI jumps return updateIn( updatedCard, - ["dataset_query", "native", "template-tags", templateTag.name], - tag => - // when we switch type, null out any default - tag.type !== templateTag.type - ? { ...templateTag, default: null } - : templateTag, + ["dataset_query", "native", "template-tags"], + tags => { + const { name } = templateTag; + const newTag = + tags[name] && tags[name].type !== templateTag.type + ? // when we switch type, null out any default + { ...templateTag, default: null } + : templateTag; + return { ...tags, [name]: newTag }; + }, ); }; }, diff --git a/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx b/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx index 18b3755e0ed..af34f0a0531 100644 --- a/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx +++ b/frontend/src/metabase/query_builder/components/NativeQueryEditor.jsx @@ -161,7 +161,10 @@ export default class NativeQueryEditor extends Component { return; } - if (this._editor.getValue() !== query.queryText()) { + // Check that the query prop changed before updating the editor. Otherwise, + // we might overwrite just typed characters before onChange is called. + const queryPropUpdated = this.props.query !== prevProps.query; + if (queryPropUpdated && this._editor.getValue() !== query.queryText()) { // This is a weird hack, but the purpose is to avoid an infinite loop caused by the fact that calling editor.setValue() // will trigger the editor 'change' event, update the query, and cause another rendering loop which we don't want, so // we need a way to update the editor without causing the onChange event to go through as well diff --git a/frontend/test/metabase/query_builder/components/NativeQueryEditor.cy.spec.js b/frontend/test/metabase/query_builder/components/NativeQueryEditor.cy.spec.js index ac957f88ab1..90204ed8a5b 100644 --- a/frontend/test/metabase/query_builder/components/NativeQueryEditor.cy.spec.js +++ b/frontend/test/metabase/query_builder/components/NativeQueryEditor.cy.spec.js @@ -64,4 +64,45 @@ describe("NativeQueryEditor", () => { "You'll need to pick a value for 'X' before this query can run.", ); }); + + it("doesn't reorder template tags when updated", () => { + cy.visit("/question/new"); + cy.contains("Native query").click(); + + // Write a query with parameter x. It defaults to a text parameter. + cy.get(".ace_content").type("{{foo}} {{bar}}", { + parseSpecialCharSequences: false, + }); + + cy.contains("Variables") + .parent() + .parent() + .find(".text-brand") + .as("variableLabels"); + + // ensure they're in the right order to start + cy.get("@variableLabels") + .first() + .should("have.text", "foo"); + cy.get("@variableLabels") + .last() + .should("have.text", "bar"); + + // change the parameter to a number. + cy.contains("Variable type") + .first() + .next() + .as("variableType"); + cy.get("@variableType").click(); + cy.contains("Number").click(); + cy.get("@variableType").should("have.text", "Number"); + + // ensure they're still in the right order + cy.get("@variableLabels") + .first() + .should("have.text", "foo"); + cy.get("@variableLabels") + .last() + .should("have.text", "bar"); + }); }); -- GitLab