From 8bc5b89fdfc47c78e2178ee9232915809cd59e36 Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Mon, 27 Feb 2023 16:31:28 +0200 Subject: [PATCH] Remove duplicate field entries for parameters in actions (#28615) --- .../src/metabase-types/api/mocks/actions.ts | 23 ++++++++ .../ActionCreator/FormCreator/FormCreator.tsx | 7 +-- .../ActionCreator/FormCreator/utils.ts | 15 +---- .../FormCreator/utils.unit.spec.ts | 46 +-------------- .../ActionCreator/tests/utils.unit.spec.ts | 50 ---------------- .../actions/containers/ActionCreator/utils.ts | 29 +++------- .../ActionCreator/utils.unit.spec.ts | 58 +++++++++++++++++++ 7 files changed, 95 insertions(+), 133 deletions(-) create mode 100644 frontend/src/metabase/actions/containers/ActionCreator/utils.unit.spec.ts diff --git a/frontend/src/metabase-types/api/mocks/actions.ts b/frontend/src/metabase-types/api/mocks/actions.ts index 66d3996413c..efc366bb2aa 100644 --- a/frontend/src/metabase-types/api/mocks/actions.ts +++ b/frontend/src/metabase-types/api/mocks/actions.ts @@ -4,6 +4,8 @@ import { WritebackParameter, WritebackQueryAction, WritebackImplicitQueryAction, + ActionFormSettings, + FieldSettings, } from "metabase-types/api"; import { createMockNativeDatasetQuery } from "./query"; import { createMockParameter } from "./parameters"; @@ -99,3 +101,24 @@ export const createMockPublicAction = ( parameters: [], ...opts, }); + +export const createMockActionFormSettings = ( + opts?: Partial<ActionFormSettings>, +): ActionFormSettings => ({ ...opts }); + +export const createMockFieldSettings = ( + opts?: Partial<FieldSettings>, +): FieldSettings => ({ + id: "", + name: "", + title: "", + description: "", + placeholder: "", + order: 0, + fieldType: "string", + inputType: "string", + required: true, + hidden: false, + width: "medium", + ...opts, +}); diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.tsx b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.tsx index 23d79cad9f0..7c9422d1b7e 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.tsx +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/FormCreator.tsx @@ -11,8 +11,7 @@ import MetabaseSettings from "metabase/lib/settings"; import type { ActionFormSettings, Parameter } from "metabase-types/api"; import { getDefaultFormSettings, sortActionParams } from "../../../utils"; -import { addMissingSettings } from "../utils"; -import { hasNewParams } from "./utils"; +import { syncFieldsWithParameters } from "../utils"; import { EmptyFormPlaceholder } from "./EmptyFormPlaceholder"; import { FormContainer, InfoText } from "./FormCreator.styled"; @@ -38,8 +37,8 @@ function FormCreator({ useEffect(() => { // add default settings for new parameters - if (formSettings && params && hasNewParams(params, formSettings)) { - setFormSettings(addMissingSettings(formSettings, params)); + if (formSettings && params) { + setFormSettings(syncFieldsWithParameters(formSettings, params)); } }, [params, formSettings]); diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.ts b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.ts index 5d6305afab7..53ea4367820 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.ts +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.ts @@ -3,12 +3,7 @@ import _ from "underscore"; import { moveElement } from "metabase/core/utils/arrays"; -import type { - ActionFormSettings, - WritebackAction, - FieldSettingsMap, - Parameter, -} from "metabase-types/api"; +import type { WritebackAction, FieldSettingsMap } from "metabase-types/api"; export const getSubmitButtonColor = (action: WritebackAction): string => { if (action.type === "implicit" && action.kind === "row/delete") { @@ -62,11 +57,3 @@ export const reorderFields = ( return _.indexBy(fieldsWithUpdatedOrderProperty, "id"); }; - -export const hasNewParams = ( - parameters: Parameter[], - formSettings: ActionFormSettings, -) => { - const fieldIds = Object.keys(formSettings.fields || {}); - return parameters.some(parameter => !fieldIds.includes(parameter.id)); -}; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.unit.spec.ts b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.unit.spec.ts index 2dd91ddbcfb..a76dfeb9fa3 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.unit.spec.ts +++ b/frontend/src/metabase/actions/containers/ActionCreator/FormCreator/utils.unit.spec.ts @@ -1,19 +1,7 @@ import type { FieldSettingsMap } from "metabase-types/api"; -import { - getDefaultFieldSettings, - getDefaultFormSettings, -} from "../../../utils"; -import { reorderFields, hasNewParams } from "./utils"; - -const createParameter = (options?: any) => { - return { - id: "test_parameter", - name: "Test Parameter", - type: "type/Text", - ...options, - }; -}; +import { getDefaultFieldSettings } from "../../../utils"; +import { reorderFields } from "./utils"; describe("actions > ActionCreator > FormCreator > utils", () => { describe("reorderFields", () => { @@ -30,34 +18,4 @@ describe("actions > ActionCreator > FormCreator > utils", () => { expect(reorderedFields.c.order).toEqual(2); }); }); - - describe("hasNewParams", () => { - const formSettings = getDefaultFormSettings({ - fields: { - a: getDefaultFieldSettings({ order: 0 }), - b: getDefaultFieldSettings({ order: 1 }), - c: getDefaultFieldSettings({ order: 2 }), - }, - }); - - it("should return true if there are new params", () => { - const params = [ - createParameter({ id: "a" }), - createParameter({ id: "b" }), - createParameter({ id: "new" }), - ]; - - expect(hasNewParams(params, formSettings)).toBe(true); - }); - - it("should return false if there are no new params", () => { - const params = [ - createParameter({ id: "a" }), - createParameter({ id: "b" }), - createParameter({ id: "c" }), - ]; - - expect(hasNewParams(params, formSettings)).toBe(false); - }); - }); }); diff --git a/frontend/src/metabase/actions/containers/ActionCreator/tests/utils.unit.spec.ts b/frontend/src/metabase/actions/containers/ActionCreator/tests/utils.unit.spec.ts index dc15d5e835c..e5c5e51a424 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/tests/utils.unit.spec.ts +++ b/frontend/src/metabase/actions/containers/ActionCreator/tests/utils.unit.spec.ts @@ -9,7 +9,6 @@ import type { TemplateTagType } from "metabase-types/types/Query"; import { getUnsavedNativeQuestion } from "metabase-lib/mocks"; import { - removeOrphanSettings, setParameterTypesFromFieldSettings, setTemplateTagTypesFromFieldSettings, } from "../utils"; @@ -41,55 +40,6 @@ const createQuestionWithTemplateTags = (tagType: TemplateTagType) => }); describe("entities > actions > utils", () => { - describe("removeOrphanSettings", () => { - it("should remove orphan settings", () => { - const formSettings = getDefaultFormSettings({ - name: "test form", - fields: { - aaa: getDefaultFieldSettings(), - bbb: getDefaultFieldSettings(), - ccc: getDefaultFieldSettings(), - }, - }); - - const parameters = [ - { id: "aaa", name: "foo" }, - { id: "ccc", name: "bar" }, - ] as Parameter[]; - - const result = removeOrphanSettings(formSettings, parameters); - - expect(result.name).toEqual("test form"); - expect(result.fields).toHaveProperty("aaa"); - expect(result.fields).toHaveProperty("ccc"); - expect(result.fields).not.toHaveProperty("bbb"); - }); - - it("should leave non-orphan settings intact", () => { - const formSettings = getDefaultFormSettings({ - name: "test form", - fields: { - aaa: getDefaultFieldSettings(), - bbb: getDefaultFieldSettings(), - ccc: getDefaultFieldSettings(), - }, - }); - - const parameters = [ - { id: "aaa", name: "foo" }, - { id: "bbb", name: "foo" }, - { id: "ccc", name: "bar" }, - ] as Parameter[]; - - const result = removeOrphanSettings(formSettings, parameters); - - expect(result.name).toEqual("test form"); - expect(result.fields).toHaveProperty("aaa"); - expect(result.fields).toHaveProperty("bbb"); - expect(result.fields).toHaveProperty("ccc"); - }); - }); - describe("setParameterTypesFromFieldSettings", () => { it("should set string parameter types", () => { const formSettings = getDefaultFormSettings({ diff --git a/frontend/src/metabase/actions/containers/ActionCreator/utils.ts b/frontend/src/metabase/actions/containers/ActionCreator/utils.ts index 14d9575737e..0a5262b32d7 100644 --- a/frontend/src/metabase/actions/containers/ActionCreator/utils.ts +++ b/frontend/src/metabase/actions/containers/ActionCreator/utils.ts @@ -117,35 +117,25 @@ export const setParameterTypesFromFieldSettings = ( }); }; -export const removeOrphanSettings = ( - formSettings: ActionFormSettings, - parameters: Parameter[], -): ActionFormSettings => { - const parameterIds = parameters.map(parameter => parameter.id); - return { - ...formSettings, - fields: _.pick(formSettings.fields || {}, parameterIds), - }; -}; - -export const addMissingSettings = ( +export const syncFieldsWithParameters = ( settings: ActionFormSettings, parameters: Parameter[], ): ActionFormSettings => { const parameterIds = parameters.map(parameter => parameter.id); const fieldIds = Object.keys(settings.fields || {}); - const missingIds = _.difference(parameterIds, fieldIds); + const addedIds = _.difference(parameterIds, fieldIds); + const removedIds = _.difference(fieldIds, parameterIds); - if (!missingIds.length) { + if (!addedIds.length && !removedIds.length) { return settings; } return { ...settings, fields: { - ...settings.fields, + ..._.omit(settings.fields, removedIds), ...Object.fromEntries( - missingIds.map(id => [id, getDefaultFieldSettings({ id })]), + addedIds.map(id => [id, getDefaultFieldSettings({ id })]), ), }, }; @@ -163,10 +153,7 @@ export const convertQuestionToAction = ( formSettings, cleanQuestion.parameters(), ); - const visualization_settings = removeOrphanSettings( - addMissingSettings(formSettings, parameters), - parameters, - ); + return { id: question.id(), name: question.displayName() as string, @@ -174,7 +161,7 @@ export const convertQuestionToAction = ( dataset_query: question.datasetQuery() as NativeDatasetQuery, database_id: question.databaseId() as DatabaseId, parameters: parameters as WritebackParameter[], - visualization_settings, + visualization_settings: formSettings, }; }; diff --git a/frontend/src/metabase/actions/containers/ActionCreator/utils.unit.spec.ts b/frontend/src/metabase/actions/containers/ActionCreator/utils.unit.spec.ts new file mode 100644 index 00000000000..42cd193f9fe --- /dev/null +++ b/frontend/src/metabase/actions/containers/ActionCreator/utils.unit.spec.ts @@ -0,0 +1,58 @@ +import { + createMockActionFormSettings, + createMockFieldSettings, + createMockParameter, +} from "metabase-types/api/mocks"; +import { syncFieldsWithParameters } from "./utils"; + +describe("syncFieldsWithParameters", () => { + it("should not modify settings if there are no changed parameters", () => { + const settings = createMockActionFormSettings({ + fields: { + u1: createMockFieldSettings({ id: "u1" }), + u2: createMockFieldSettings({ id: "u2" }), + }, + }); + const parameters = [ + createMockParameter({ + id: "u1", + }), + createMockParameter({ + id: "u2", + }), + ]; + + const newSettings = syncFieldsWithParameters(settings, parameters); + + expect(newSettings).toBe(settings); + }); + + it("should add new fields and remove non-existing ones", () => { + const settings = createMockActionFormSettings({ + fields: { + u1: createMockFieldSettings({ id: "u1" }), + u2: createMockFieldSettings({ id: "u2", fieldType: "number" }), + }, + }); + const parameters = [ + createMockParameter({ + id: "u2", + }), + createMockParameter({ + id: "u3", + }), + ]; + + const newSettings = syncFieldsWithParameters(settings, parameters); + + expect(newSettings).toMatchObject({ + fields: { + u2: { id: "u2", fieldType: "number" }, + u3: { id: "u3", fieldType: "string" }, + }, + }); + expect(newSettings).not.toMatchObject({ + u1: { id: "u1" }, + }); + }); +}); -- GitLab