From 142b872bee16d3bd11d56871780b6555cd849971 Mon Sep 17 00:00:00 2001 From: Ezra Odio Date: Tue, 11 Jun 2024 16:57:34 +0000 Subject: [PATCH 1/3] Add new text pattern parameter --- .../EditParameterSettingsDialog.jsx | 28 ++++++++ .../EditParameterSettingsDialog.less | 3 + .../app/components/ParameterMappingInput.jsx | 1 + client/app/components/ParameterValueInput.jsx | 23 +++++++ client/app/components/Parameters.jsx | 1 + .../parameters/TextPatternParameter.js | 29 +++++++++ client/app/services/parameters/index.js | 4 ++ .../parameters/tests/Parameter.test.js | 2 + .../tests/TextPatternParameter.test.js | 21 ++++++ .../integration/query/parameter_spec.js | 65 +++++++++++++++++++ redash/models/parameterized_query.py | 13 ++++ tests/models/test_parameterized_query.py | 15 +++++ 12 files changed, 205 insertions(+) create mode 100644 client/app/components/EditParameterSettingsDialog.less create mode 100644 client/app/services/parameters/TextPatternParameter.js create mode 100644 client/app/services/parameters/tests/TextPatternParameter.test.js diff --git a/client/app/components/EditParameterSettingsDialog.jsx b/client/app/components/EditParameterSettingsDialog.jsx index a206c1fc7d..d620883549 100644 --- a/client/app/components/EditParameterSettingsDialog.jsx +++ b/client/app/components/EditParameterSettingsDialog.jsx @@ -12,6 +12,7 @@ import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import QuerySelector from "@/components/QuerySelector"; import { Query } from "@/services/query"; import { useUniqueId } from "@/lib/hooks/useUniqueId"; +import "./EditParameterSettingsDialog.less"; const { Option } = Select; const formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } }; @@ -71,6 +72,8 @@ function EditParameterSettingsDialog(props) { const [param, setParam] = useState(clone(props.parameter)); const [isNameValid, setIsNameValid] = useState(true); const [initialQuery, setInitialQuery] = useState(); + const [userInput, setUserInput] = useState(param.regex || ""); + const [isValidRegex, setIsValidRegex] = useState(true); const isNew = !props.parameter.name; @@ -114,6 +117,17 @@ function EditParameterSettingsDialog(props) { const paramFormId = useUniqueId("paramForm"); + const handleRegexChange = e => { + setUserInput(e.target.value); + try { + new RegExp(e.target.value); + setParam({ ...param, regex: e.target.value }); + setIsValidRegex(true); + } catch (error) { + setIsValidRegex(false); + } + }; + return ( Text + @@ -180,6 +195,19 @@ function EditParameterSettingsDialog(props) { + {param.type === "text-pattern" && ( + + + + )} {param.type === "enum" && ( this.updateParamMapping({ value })} + regex={mapping.param.regex} /> ); } diff --git a/client/app/components/ParameterValueInput.jsx b/client/app/components/ParameterValueInput.jsx index f2ad8c7a94..5969e07926 100644 --- a/client/app/components/ParameterValueInput.jsx +++ b/client/app/components/ParameterValueInput.jsx @@ -9,6 +9,7 @@ import DateRangeParameter from "@/components/dynamic-parameters/DateRangeParamet import QueryBasedParameterInput from "./QueryBasedParameterInput"; import "./ParameterValueInput.less"; +import Tooltip from "./Tooltip"; const multipleValuesProps = { maxTagCount: 3, @@ -25,6 +26,7 @@ class ParameterValueInput extends React.Component { parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types onSelect: PropTypes.func, className: PropTypes.string, + regex: PropTypes.string, }; static defaultProps = { @@ -35,6 +37,7 @@ class ParameterValueInput extends React.Component { parameter: null, onSelect: () => {}, className: "", + regex: "", }; constructor(props) { @@ -145,6 +148,24 @@ class ParameterValueInput extends React.Component { ); } + renderTextPatternInput() { + const { className } = this.props; + const { value } = this.state; + + return ( + + + this.onSelect(e.target.value)} + /> + + + ); + } + renderTextInput() { const { className } = this.props; const { value } = this.state; @@ -177,6 +198,8 @@ class ParameterValueInput extends React.Component { return this.renderQueryBasedInput(); case "number": return this.renderNumberInput(); + case "text-pattern": + return this.renderTextPatternInput(); default: return this.renderTextInput(); } diff --git a/client/app/components/Parameters.jsx b/client/app/components/Parameters.jsx index 2e504bba32..d94bf27ce5 100644 --- a/client/app/components/Parameters.jsx +++ b/client/app/components/Parameters.jsx @@ -162,6 +162,7 @@ export default class Parameters extends React.Component { enumOptions={param.enumOptions} queryId={param.queryId} onSelect={(value, isDirty) => this.setPendingValue(param, value, isDirty)} + regex={param.regex} /> ); diff --git a/client/app/services/parameters/TextPatternParameter.js b/client/app/services/parameters/TextPatternParameter.js new file mode 100644 index 0000000000..fe84c00ce8 --- /dev/null +++ b/client/app/services/parameters/TextPatternParameter.js @@ -0,0 +1,29 @@ +import { toString, isNull } from "lodash"; +import Parameter from "./Parameter"; + +class TextPatternParameter extends Parameter { + constructor(parameter, parentQueryId) { + super(parameter, parentQueryId); + this.regex = parameter.regex; + this.setValue(parameter.value); + } + + // eslint-disable-next-line class-methods-use-this + normalizeValue(value) { + const normalizedValue = toString(value); + if (isNull(normalizedValue)) { + return null; + } + + var re = new RegExp(this.regex); + + if (re !== null) { + if (re.test(normalizedValue)) { + return normalizedValue; + } + } + return null; + } +} + +export default TextPatternParameter; diff --git a/client/app/services/parameters/index.js b/client/app/services/parameters/index.js index 9e1b3fffcb..e34eced08f 100644 --- a/client/app/services/parameters/index.js +++ b/client/app/services/parameters/index.js @@ -5,6 +5,7 @@ import EnumParameter from "./EnumParameter"; import QueryBasedDropdownParameter from "./QueryBasedDropdownParameter"; import DateParameter from "./DateParameter"; import DateRangeParameter from "./DateRangeParameter"; +import TextPatternParameter from "./TextPatternParameter"; function createParameter(param, parentQueryId) { switch (param.type) { @@ -22,6 +23,8 @@ function createParameter(param, parentQueryId) { case "datetime-range": case "datetime-range-with-seconds": return new DateRangeParameter(param, parentQueryId); + case "text-pattern": + return new TextPatternParameter({ ...param, type: "text-pattern" }, parentQueryId); default: return new TextParameter({ ...param, type: "text" }, parentQueryId); } @@ -34,6 +37,7 @@ function cloneParameter(param) { export { Parameter, TextParameter, + TextPatternParameter, NumberParameter, EnumParameter, QueryBasedDropdownParameter, diff --git a/client/app/services/parameters/tests/Parameter.test.js b/client/app/services/parameters/tests/Parameter.test.js index 3ec7ad7b13..4d504e3165 100644 --- a/client/app/services/parameters/tests/Parameter.test.js +++ b/client/app/services/parameters/tests/Parameter.test.js @@ -1,6 +1,7 @@ import { createParameter, TextParameter, + TextPatternParameter, NumberParameter, EnumParameter, QueryBasedDropdownParameter, @@ -12,6 +13,7 @@ describe("Parameter", () => { describe("create", () => { const parameterTypes = [ ["text", TextParameter], + ["text-pattern", TextPatternParameter], ["number", NumberParameter], ["enum", EnumParameter], ["query", QueryBasedDropdownParameter], diff --git a/client/app/services/parameters/tests/TextPatternParameter.test.js b/client/app/services/parameters/tests/TextPatternParameter.test.js new file mode 100644 index 0000000000..699e320d96 --- /dev/null +++ b/client/app/services/parameters/tests/TextPatternParameter.test.js @@ -0,0 +1,21 @@ +import { createParameter } from ".."; + +describe("TextPatternParameter", () => { + let param; + + beforeEach(() => { + param = createParameter({ name: "param", title: "Param", type: "text-pattern", regex: "a+" }); + }); + + describe("noramlizeValue", () => { + test("converts matching strings", () => { + const normalizedValue = param.normalizeValue("art"); + expect(normalizedValue).toBe("art"); + }); + + test("returns null when string does not match pattern", () => { + const normalizedValue = param.normalizeValue("brt"); + expect(normalizedValue).toBeNull(); + }); + }); +}); diff --git a/client/cypress/integration/query/parameter_spec.js b/client/cypress/integration/query/parameter_spec.js index 93fcecb1de..7108b00d3b 100644 --- a/client/cypress/integration/query/parameter_spec.js +++ b/client/cypress/integration/query/parameter_spec.js @@ -60,6 +60,71 @@ describe("Parameter", () => { }); }); + describe("Text Pattern Parameter", () => { + beforeEach(() => { + const queryData = { + name: "Text Pattern Parameter", + query: "SELECT '{{test-parameter}}' AS parameter", + options: { + parameters: [{ name: "test-parameter", title: "Test Parameter", type: "text-pattern", regex: "a+" }], + }, + }; + + cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`)); + }); + + it("updates the results after clicking Apply", () => { + cy.getByTestId("ParameterName-test-parameter") + .find("input") + .type("{selectall}art"); + + cy.getByTestId("ParameterApplyButton").click(); + + cy.getByTestId("TableVisualization").should("contain", "art"); + + cy.getByTestId("ParameterName-test-parameter") + .find("input") + .type("{selectall}around"); + + cy.getByTestId("ParameterApplyButton").click(); + + cy.getByTestId("TableVisualization").should("contain", "around"); + }); + + it("throws error message with invalid query request", () => { + cy.getByTestId("ParameterName-test-parameter") + .find("input") + .type("{selectall}art"); + + cy.getByTestId("ParameterApplyButton").click(); + + cy.getByTestId("ParameterName-test-parameter") + .find("input") + .type("{selectall}test"); + + cy.getByTestId("ParameterApplyButton").click(); + + cy.getByTestId("QueryExecutionStatus").should("exist"); + }); + + it("sets dirty state when edited", () => { + expectDirtyStateChange(() => { + cy.getByTestId("ParameterName-test-parameter") + .find("input") + .type("{selectall}art"); + }); + }); + + it("doesn't let user save invalid regex", () => { + cy.get(".fa-cog").click(); + cy.getByTestId("RegexPatternInput").type("{selectall}["); + cy.contains("Invalid Regex Pattern").should("exist"); + cy.getByTestId("SaveParameterSettings").click(); + cy.get(".fa-cog").click(); + cy.getByTestId("RegexPatternInput").should("not.equal", "["); + }); + }); + describe("Number Parameter", () => { beforeEach(() => { const queryData = { diff --git a/redash/models/parameterized_query.py b/redash/models/parameterized_query.py index a3a3fe5943..6799296675 100644 --- a/redash/models/parameterized_query.py +++ b/redash/models/parameterized_query.py @@ -1,3 +1,4 @@ +import re from functools import partial from numbers import Number @@ -88,6 +89,16 @@ def _is_number(string): return True +def _is_regex_pattern(value, regex): + try: + if re.compile(regex).fullmatch(value): + return True + else: + return False + except re.error: + return False + + def _is_date(string): parse(string) return True @@ -135,6 +146,7 @@ def _valid(self, name, value): enum_options = definition.get("enumOptions") query_id = definition.get("queryId") + regex = definition.get("regex") allow_multiple_values = isinstance(definition.get("multiValuesOptions"), dict) if isinstance(enum_options, str): @@ -142,6 +154,7 @@ def _valid(self, name, value): validators = { "text": lambda value: isinstance(value, str), + "text-pattern": lambda value: _is_regex_pattern(value, regex), "number": _is_number, "enum": lambda value: _is_value_within_options(value, enum_options, allow_multiple_values), "query": lambda value: _is_value_within_options( diff --git a/tests/models/test_parameterized_query.py b/tests/models/test_parameterized_query.py index 2293ab81fa..b46ea19003 100644 --- a/tests/models/test_parameterized_query.py +++ b/tests/models/test_parameterized_query.py @@ -73,6 +73,21 @@ def test_validates_text_parameters(self): self.assertEqual("foo baz", query.text) + def test_validates_text_pattern_parameters(self): + schema = [{"name": "bar", "type": "text-pattern", "regex": "a+"}] + query = ParameterizedQuery("foo {{bar}}", schema) + + query.apply({"bar": "a"}) + + self.assertEqual("foo a", query.text) + + def test_raises_on_invalid_text_pattern_parameters(self): + schema = schema = [{"name": "bar", "type": "text-pattern", "regex": "a+"}] + query = ParameterizedQuery("foo {{bar}}", schema) + + with pytest.raises(InvalidParameterError): + query.apply({"bar": "b"}) + def test_raises_on_invalid_number_parameters(self): schema = [{"name": "bar", "type": "number"}] query = ParameterizedQuery("foo", schema) From cacb8bfd8b2b11756d6f0ece2202da667cd6be3e Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Wed, 24 Jul 2024 14:18:12 +0000 Subject: [PATCH 2/3] Restyled by prettier --- .../EditParameterSettingsDialog.jsx | 38 ++-- .../app/components/ParameterMappingInput.jsx | 54 ++--- client/app/components/ParameterValueInput.jsx | 20 +- client/app/components/Parameters.jsx | 29 +-- .../integration/query/parameter_spec.js | 207 ++++++------------ 5 files changed, 137 insertions(+), 211 deletions(-) diff --git a/client/app/components/EditParameterSettingsDialog.jsx b/client/app/components/EditParameterSettingsDialog.jsx index d620883549..f182bb9d1e 100644 --- a/client/app/components/EditParameterSettingsDialog.jsx +++ b/client/app/components/EditParameterSettingsDialog.jsx @@ -27,7 +27,7 @@ function isTypeDateRange(type) { function joinExampleList(multiValuesOptions) { const { prefix, suffix } = multiValuesOptions; - return ["value1", "value2", "value3"].map(value => `${prefix}${value}${suffix}`).join(","); + return ["value1", "value2", "value3"].map((value) => `${prefix}${value}${suffix}`).join(","); } function NameInput({ name, type, onChange, existingNames, setValidation }) { @@ -55,7 +55,7 @@ function NameInput({ name, type, onChange, existingNames, setValidation }) { return ( - onChange(e.target.value)} autoFocus /> + onChange(e.target.value)} autoFocus /> ); } @@ -117,7 +117,7 @@ function EditParameterSettingsDialog(props) { const paramFormId = useUniqueId("paramForm"); - const handleRegexChange = e => { + const handleRegexChange = (e) => { setUserInput(e.target.value); try { new RegExp(e.target.value); @@ -143,15 +143,17 @@ function EditParameterSettingsDialog(props) { disabled={!isFulfilled()} type="primary" form={paramFormId} - data-test="SaveParameterSettings"> + data-test="SaveParameterSettings" + > {isNew ? "Add Parameter" : "OK"} , - ]}> + ]} + >
{isNew && ( setParam({ ...param, name })} + onChange={(name) => setParam({ ...param, name })} setValidation={setIsNameValid} existingNames={props.existingParams} type={param.type} @@ -160,12 +162,12 @@ function EditParameterSettingsDialog(props) { setParam({ ...param, title: e.target.value })} + onChange={(e) => setParam({ ...param, title: e.target.value })} data-test="ParameterTitleInput" /> - setParam({ ...param, type })} data-test="ParameterTypeSelect"> @@ -199,7 +201,8 @@ function EditParameterSettingsDialog(props) { + {...formItemProps} + > setParam({ ...param, enumOptions: e.target.value })} + onChange={(e) => setParam({ ...param, enumOptions: e.target.value })} /> )} @@ -221,7 +224,7 @@ function EditParameterSettingsDialog(props) { setParam({ ...param, queryId: q && q.id })} + onChange={(q) => setParam({ ...param, queryId: q && q.id })} type="select" /> @@ -230,7 +233,7 @@ function EditParameterSettingsDialog(props) { + onChange={(e) => setParam({ ...param, multiValuesOptions: e.target.checked @@ -242,7 +245,8 @@ function EditParameterSettingsDialog(props) { : null, }) } - data-test="AllowMultipleValuesCheckbox"> + data-test="AllowMultipleValuesCheckbox" + > Allow multiple values @@ -255,10 +259,11 @@ function EditParameterSettingsDialog(props) { Placed in query as: {joinExampleList(param.multiValuesOptions)} } - {...formItemProps}> + {...formItemProps} + > this.updateParamMapping({ mapTo: e.target.value })} + onChange={(e) => this.updateParamMapping({ mapTo: e.target.value })} /> ); } renderDashboardMapToExisting() { const { mapping, existingParamNames } = this.props; - const options = map(existingParamNames, paramName => ({ label: paramName, value: paramName })); + const options = map(existingParamNames, (paramName) => ({ label: paramName, value: paramName })); - return this.updateParamMapping({ mapTo })} options={options} />; } renderStaticValue() { @@ -226,7 +226,7 @@ export class ParameterMappingInput extends React.Component { enumOptions={mapping.param.enumOptions} queryId={mapping.param.queryId} parameter={mapping.param} - onSelect={value => this.updateParamMapping({ value })} + onSelect={(value) => this.updateParamMapping({ value })} regex={mapping.param.regex} /> ); @@ -285,12 +285,12 @@ class MappingEditor extends React.Component { }; } - onVisibleChange = visible => { + onVisibleChange = (visible) => { if (visible) this.show(); else this.hide(); }; - onChange = mapping => { + onChange = (mapping) => { let inputError = null; if (mapping.type === MappingType.DashboardAddNew) { @@ -352,7 +352,8 @@ class MappingEditor extends React.Component { trigger="click" content={this.renderContent()} visible={visible} - onVisibleChange={this.onVisibleChange}> + onVisibleChange={this.onVisibleChange} + > @@ -377,14 +378,14 @@ class TitleEditor extends React.Component { title: "", // will be set on editing }; - onPopupVisibleChange = showPopup => { + onPopupVisibleChange = (showPopup) => { this.setState({ showPopup, title: showPopup ? this.getMappingTitle() : "", }); }; - onEditingTitleChange = event => { + onEditingTitleChange = (event) => { this.setState({ title: event.target.value }); }; @@ -461,7 +462,8 @@ class TitleEditor extends React.Component { trigger="click" content={this.renderPopover()} visible={this.state.showPopup} - onVisibleChange={this.onPopupVisibleChange}> + onVisibleChange={this.onPopupVisibleChange} + > @@ -509,7 +511,7 @@ export class ParameterMappingListInput extends React.Component { // just to be safe, array or object if (typeof value === "object") { - return map(value, v => this.getStringValue(v)).join(", "); + return map(value, (v) => this.getStringValue(v)).join(", "); } // rest @@ -575,7 +577,7 @@ export class ParameterMappingListInput extends React.Component { render() { const { existingParams } = this.props; // eslint-disable-line react/prop-types - const dataSource = this.props.mappings.map(mapping => ({ mapping })); + const dataSource = this.props.mappings.map((mapping) => ({ mapping })); return (
@@ -584,11 +586,11 @@ export class ParameterMappingListInput extends React.Component { title="Title" dataIndex="mapping" key="title" - render={mapping => ( + render={(mapping) => ( this.updateParamMapping(mapping, newMapping)} + onChange={(newMapping) => this.updateParamMapping(mapping, newMapping)} /> )} /> @@ -597,19 +599,19 @@ export class ParameterMappingListInput extends React.Component { dataIndex="mapping" key="keyword" className="keyword" - render={mapping => {`{{ ${mapping.name} }}`}} + render={(mapping) => {`{{ ${mapping.name} }}`}} /> this.constructor.getDefaultValue(mapping, this.props.existingParams)} + render={(mapping) => this.constructor.getDefaultValue(mapping, this.props.existingParams)} /> { + render={(mapping) => { const existingParamsNames = existingParams .filter(({ type }) => type === mapping.param.type) // exclude mismatching param types .map(({ name }) => name); // keep names only diff --git a/client/app/components/ParameterValueInput.jsx b/client/app/components/ParameterValueInput.jsx index 5969e07926..894530e30b 100644 --- a/client/app/components/ParameterValueInput.jsx +++ b/client/app/components/ParameterValueInput.jsx @@ -14,7 +14,7 @@ import Tooltip from "./Tooltip"; const multipleValuesProps = { maxTagCount: 3, maxTagTextLength: 10, - maxTagPlaceholder: num => `+${num.length} more`, + maxTagPlaceholder: (num) => `+${num.length} more`, }; class ParameterValueInput extends React.Component { @@ -48,7 +48,7 @@ class ParameterValueInput extends React.Component { }; } - componentDidUpdate = prevProps => { + componentDidUpdate = (prevProps) => { const { value, parameter } = this.props; // if value prop updated, reset dirty state if (prevProps.value !== value || prevProps.parameter !== parameter) { @@ -59,7 +59,7 @@ class ParameterValueInput extends React.Component { } }; - onSelect = value => { + onSelect = (value) => { const isDirty = !isEqual(value, this.props.value); this.setState({ value, isDirty }); this.props.onSelect(value, isDirty); @@ -96,9 +96,9 @@ class ParameterValueInput extends React.Component { renderEnumInput() { const { enumOptions, parameter } = this.props; const { value } = this.state; - const enumOptionsArray = enumOptions.split("\n").filter(v => v !== ""); + const enumOptionsArray = enumOptions.split("\n").filter((v) => v !== ""); // Antd Select doesn't handle null in multiple mode - const normalize = val => (parameter.multiValuesOptions && val === null ? [] : val); + const normalize = (val) => (parameter.multiValuesOptions && val === null ? [] : val); return ( ({ label: String(opt), value: opt }))} + options={map(enumOptionsArray, (opt) => ({ label: String(opt), value: opt }))} showSearch showArrow notFoundContent={isEmpty(enumOptionsArray) ? "No options available" : null} @@ -136,14 +136,14 @@ class ParameterValueInput extends React.Component { const { className } = this.props; const { value } = this.state; - const normalize = val => (isNaN(val) ? undefined : val); + const normalize = (val) => (isNaN(val) ? undefined : val); return ( this.onSelect(normalize(val))} + onChange={(val) => this.onSelect(normalize(val))} /> ); } @@ -159,7 +159,7 @@ class ParameterValueInput extends React.Component { className={className} value={value} aria-label="Parameter text pattern value" - onChange={e => this.onSelect(e.target.value)} + onChange={(e) => this.onSelect(e.target.value)} /> @@ -176,7 +176,7 @@ class ParameterValueInput extends React.Component { value={value} aria-label="Parameter text value" data-test="TextParamInput" - onChange={e => this.onSelect(e.target.value)} + onChange={(e) => this.onSelect(e.target.value)} /> ); } diff --git a/client/app/components/Parameters.jsx b/client/app/components/Parameters.jsx index d94bf27ce5..ef4d30ed45 100644 --- a/client/app/components/Parameters.jsx +++ b/client/app/components/Parameters.jsx @@ -14,7 +14,7 @@ import "./Parameters.less"; function updateUrl(parameters) { const params = extend({}, location.search); - parameters.forEach(param => { + parameters.forEach((param) => { extend(params, param.toUrlParams()); }); location.setSearch(params, true); @@ -43,7 +43,7 @@ export default class Parameters extends React.Component { appendSortableToParent: true, }; - toCamelCase = str => { + toCamelCase = (str) => { if (isEmpty(str)) { return ""; } @@ -59,10 +59,10 @@ export default class Parameters extends React.Component { } const hideRegex = /hide_filter=([^&]+)/g; const matches = window.location.search.matchAll(hideRegex); - this.hideValues = Array.from(matches, match => match[1]); + this.hideValues = Array.from(matches, (match) => match[1]); } - componentDidUpdate = prevProps => { + componentDidUpdate = (prevProps) => { const { parameters, disableUrlUpdate } = this.props; const parametersChanged = prevProps.parameters !== parameters; const disableUrlUpdateChanged = prevProps.disableUrlUpdate !== disableUrlUpdate; @@ -74,7 +74,7 @@ export default class Parameters extends React.Component { } }; - handleKeyDown = e => { + handleKeyDown = (e) => { // Cmd/Ctrl/Alt + Enter if (e.keyCode === 13 && (e.ctrlKey || e.metaKey || e.altKey)) { e.stopPropagation(); @@ -109,8 +109,8 @@ export default class Parameters extends React.Component { applyChanges = () => { const { onValuesChange, disableUrlUpdate } = this.props; this.setState(({ parameters }) => { - const parametersWithPendingValues = parameters.filter(p => p.hasPendingValue); - forEach(parameters, p => p.applyPendingValue()); + const parametersWithPendingValues = parameters.filter((p) => p.hasPendingValue); + forEach(parameters, (p) => p.applyPendingValue()); if (!disableUrlUpdate) { updateUrl(parameters); } @@ -121,7 +121,7 @@ export default class Parameters extends React.Component { showParameterSettings = (parameter, index) => { const { onParametersEdit } = this.props; - EditParameterSettingsDialog.showModal({ parameter }).onClose(updated => { + EditParameterSettingsDialog.showModal({ parameter }).onClose((updated) => { this.setState(({ parameters }) => { const updatedParameter = extend(parameter, updated); parameters[index] = createParameter(updatedParameter, updatedParameter.parentQueryId); @@ -132,7 +132,7 @@ export default class Parameters extends React.Component { }; renderParameter(param, index) { - if (this.hideValues.some(value => this.toCamelCase(value) === this.toCamelCase(param.name))) { + if (this.hideValues.some((value) => this.toCamelCase(value) === this.toCamelCase(param.name))) { return null; } const { editable } = this.props; @@ -149,7 +149,8 @@ export default class Parameters extends React.Component { aria-label="Edit" onClick={() => this.showParameterSettings(param, index)} data-test={`ParameterSettings-${param.name}`} - type="button"> + type="button" + >