diff --git a/client/app/components/EditParameterSettingsDialog.jsx b/client/app/components/EditParameterSettingsDialog.jsx index a206c1fc7d..f182bb9d1e 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 } }; @@ -26,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 }) { @@ -54,7 +55,7 @@ function NameInput({ name, type, onChange, existingNames, setValidation }) { return ( - onChange(e.target.value)} autoFocus /> + onChange(e.target.value)} autoFocus /> ); } @@ -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 ( + data-test="SaveParameterSettings" + > {isNew ? "Add Parameter" : "OK"} , - ]}> + ]} + >
{isNew && ( setParam({ ...param, name })} + onChange={(name) => setParam({ ...param, name })} setValidation={setIsNameValid} existingNames={props.existingParams} type={param.type} @@ -146,15 +162,16 @@ 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"> + @@ -180,12 +197,26 @@ function EditParameterSettingsDialog(props) { + {param.type === "text-pattern" && ( + + + + )} {param.type === "enum" && ( setParam({ ...param, enumOptions: e.target.value })} + onChange={(e) => setParam({ ...param, enumOptions: e.target.value })} /> )} @@ -193,7 +224,7 @@ function EditParameterSettingsDialog(props) { setParam({ ...param, queryId: q && q.id })} + onChange={(q) => setParam({ ...param, queryId: q && q.id })} type="select" /> @@ -202,7 +233,7 @@ function EditParameterSettingsDialog(props) { + onChange={(e) => setParam({ ...param, multiValuesOptions: e.target.checked @@ -214,7 +245,8 @@ function EditParameterSettingsDialog(props) { : null, }) } - data-test="AllowMultipleValuesCheckbox"> + data-test="AllowMultipleValuesCheckbox" + > Allow multiple values @@ -227,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,8 @@ 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} /> ); } @@ -284,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) { @@ -351,7 +352,8 @@ class MappingEditor extends React.Component { trigger="click" content={this.renderContent()} visible={visible} - onVisibleChange={this.onVisibleChange}> + onVisibleChange={this.onVisibleChange} + > @@ -376,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 }); }; @@ -460,7 +462,8 @@ class TitleEditor extends React.Component { trigger="click" content={this.renderPopover()} visible={this.state.showPopup} - onVisibleChange={this.onPopupVisibleChange}> + onVisibleChange={this.onPopupVisibleChange} + > @@ -508,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 @@ -574,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 (
@@ -583,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)} /> )} /> @@ -596,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 f2ad8c7a94..894530e30b 100644 --- a/client/app/components/ParameterValueInput.jsx +++ b/client/app/components/ParameterValueInput.jsx @@ -9,11 +9,12 @@ import DateRangeParameter from "@/components/dynamic-parameters/DateRangeParamet import QueryBasedParameterInput from "./QueryBasedParameterInput"; import "./ParameterValueInput.less"; +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 { @@ -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) { @@ -45,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) { @@ -56,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); @@ -93,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} @@ -133,18 +136,36 @@ 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))} /> ); } + renderTextPatternInput() { + const { className } = this.props; + const { value } = this.state; + + return ( + + + this.onSelect(e.target.value)} + /> + + + ); + } + renderTextInput() { const { className } = this.props; const { value } = this.state; @@ -155,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)} /> ); } @@ -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..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" + >