Skip to content

Commit

Permalink
Add new text pattern parameter (#7025)
Browse files Browse the repository at this point in the history
Co-authored-by: Ezra Odio <eodio@starfishstorage.com>
Co-authored-by: Restyled.io <commits@restyled.io>
  • Loading branch information
3 people authored Jul 24, 2024
1 parent c244e75 commit a69f7fb
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 193 deletions.
62 changes: 48 additions & 14 deletions client/app/components/EditParameterSettingsDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } };
Expand All @@ -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 }) {
Expand Down Expand Up @@ -54,7 +55,7 @@ function NameInput({ name, type, onChange, existingNames, setValidation }) {

return (
<Form.Item required label="Keyword" help={helpText} validateStatus={validateStatus} {...formItemProps}>
<Input onChange={e => onChange(e.target.value)} autoFocus />
<Input onChange={(e) => onChange(e.target.value)} autoFocus />
</Form.Item>
);
}
Expand All @@ -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;

Expand Down Expand Up @@ -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 (
<Modal
{...props.dialog.props}
Expand All @@ -129,15 +143,17 @@ function EditParameterSettingsDialog(props) {
disabled={!isFulfilled()}
type="primary"
form={paramFormId}
data-test="SaveParameterSettings">
data-test="SaveParameterSettings"
>
{isNew ? "Add Parameter" : "OK"}
</Button>,
]}>
]}
>
<Form layout="horizontal" onFinish={onConfirm} id={paramFormId}>
{isNew && (
<NameInput
name={param.name}
onChange={name => setParam({ ...param, name })}
onChange={(name) => setParam({ ...param, name })}
setValidation={setIsNameValid}
existingNames={props.existingParams}
type={param.type}
Expand All @@ -146,15 +162,16 @@ function EditParameterSettingsDialog(props) {
<Form.Item required label="Title" {...formItemProps}>
<Input
value={isNull(param.title) ? getDefaultTitle(param.name) : param.title}
onChange={e => setParam({ ...param, title: e.target.value })}
onChange={(e) => setParam({ ...param, title: e.target.value })}
data-test="ParameterTitleInput"
/>
</Form.Item>
<Form.Item label="Type" {...formItemProps}>
<Select value={param.type} onChange={type => setParam({ ...param, type })} data-test="ParameterTypeSelect">
<Select value={param.type} onChange={(type) => setParam({ ...param, type })} data-test="ParameterTypeSelect">
<Option value="text" data-test="TextParameterTypeOption">
Text
</Option>
<Option value="text-pattern">Text Pattern</Option>
<Option value="number" data-test="NumberParameterTypeOption">
Number
</Option>
Expand All @@ -180,20 +197,34 @@ function EditParameterSettingsDialog(props) {
<Option value="datetime-range-with-seconds">Date and Time Range (with seconds)</Option>
</Select>
</Form.Item>
{param.type === "text-pattern" && (
<Form.Item
label="Regex"
help={!isValidRegex ? "Invalid Regex Pattern" : "Valid Regex Pattern"}
{...formItemProps}
>
<Input
value={userInput}
onChange={handleRegexChange}
className={!isValidRegex ? "input-error" : ""}
data-test="RegexPatternInput"
/>
</Form.Item>
)}
{param.type === "enum" && (
<Form.Item label="Values" help="Dropdown list values (newline delimited)" {...formItemProps}>
<Input.TextArea
rows={3}
value={param.enumOptions}
onChange={e => setParam({ ...param, enumOptions: e.target.value })}
onChange={(e) => setParam({ ...param, enumOptions: e.target.value })}
/>
</Form.Item>
)}
{param.type === "query" && (
<Form.Item label="Query" help="Select query to load dropdown values from" {...formItemProps}>
<QuerySelector
selectedQuery={initialQuery}
onChange={q => setParam({ ...param, queryId: q && q.id })}
onChange={(q) => setParam({ ...param, queryId: q && q.id })}
type="select"
/>
</Form.Item>
Expand All @@ -202,7 +233,7 @@ function EditParameterSettingsDialog(props) {
<Form.Item className="m-b-0" label=" " colon={false} {...formItemProps}>
<Checkbox
defaultChecked={!!param.multiValuesOptions}
onChange={e =>
onChange={(e) =>
setParam({
...param,
multiValuesOptions: e.target.checked
Expand All @@ -214,7 +245,8 @@ function EditParameterSettingsDialog(props) {
: null,
})
}
data-test="AllowMultipleValuesCheckbox">
data-test="AllowMultipleValuesCheckbox"
>
Allow multiple values
</Checkbox>
</Form.Item>
Expand All @@ -227,10 +259,11 @@ function EditParameterSettingsDialog(props) {
Placed in query as: <code>{joinExampleList(param.multiValuesOptions)}</code>
</React.Fragment>
}
{...formItemProps}>
{...formItemProps}
>
<Select
value={param.multiValuesOptions.prefix}
onChange={quoteOption =>
onChange={(quoteOption) =>
setParam({
...param,
multiValuesOptions: {
Expand All @@ -240,7 +273,8 @@ function EditParameterSettingsDialog(props) {
},
})
}
data-test="QuotationSelect">
data-test="QuotationSelect"
>
<Option value="">None (default)</Option>
<Option value="'">Single Quotation Mark</Option>
<Option value={'"'} data-test="DoubleQuotationMarkOption">
Expand Down
3 changes: 3 additions & 0 deletions client/app/components/EditParameterSettingsDialog.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.input-error {
border-color: red !important;
}
55 changes: 29 additions & 26 deletions client/app/components/ParameterMappingInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export const MappingType = {
};

export function parameterMappingsToEditableMappings(mappings, parameters, existingParameterNames = []) {
return map(mappings, mapping => {
return map(mappings, (mapping) => {
const result = extend({}, mapping);
const alreadyExists = includes(existingParameterNames, mapping.mapTo);
result.param = find(parameters, p => p.name === mapping.name);
result.param = find(parameters, (p) => p.name === mapping.name);
switch (mapping.type) {
case ParameterMappingType.DashboardLevel:
result.type = alreadyExists ? MappingType.DashboardMapToExisting : MappingType.DashboardAddNew;
Expand All @@ -62,7 +62,7 @@ export function editableMappingsToParameterMappings(mappings) {
map(
// convert to map
mappings,
mapping => {
(mapping) => {
const result = extend({}, mapping);
switch (mapping.type) {
case MappingType.DashboardAddNew:
Expand Down Expand Up @@ -95,11 +95,11 @@ export function editableMappingsToParameterMappings(mappings) {
export function synchronizeWidgetTitles(sourceMappings, widgets) {
const affectedWidgets = [];

each(sourceMappings, sourceMapping => {
each(sourceMappings, (sourceMapping) => {
if (sourceMapping.type === ParameterMappingType.DashboardLevel) {
each(widgets, widget => {
each(widgets, (widget) => {
const widgetMappings = widget.options.parameterMappings;
each(widgetMappings, widgetMapping => {
each(widgetMappings, (widgetMapping) => {
// check if mapped to the same dashboard-level parameter
if (
widgetMapping.type === ParameterMappingType.DashboardLevel &&
Expand Down Expand Up @@ -140,7 +140,7 @@ export class ParameterMappingInput extends React.Component {
className: "form-item",
};

updateSourceType = type => {
updateSourceType = (type) => {
let {
mapping: { mapTo },
} = this.props;
Expand All @@ -155,7 +155,7 @@ export class ParameterMappingInput extends React.Component {
this.updateParamMapping({ type, mapTo });
};

updateParamMapping = update => {
updateParamMapping = (update) => {
const { onChange, mapping } = this.props;
const newMapping = extend({}, mapping, update);
if (newMapping.value !== mapping.value) {
Expand All @@ -175,7 +175,7 @@ export class ParameterMappingInput extends React.Component {
renderMappingTypeSelector() {
const noExisting = isEmpty(this.props.existingParamNames);
return (
<Radio.Group value={this.props.mapping.type} onChange={e => this.updateSourceType(e.target.value)}>
<Radio.Group value={this.props.mapping.type} onChange={(e) => this.updateSourceType(e.target.value)}>
<Radio className="radio" value={MappingType.DashboardAddNew} data-test="NewDashboardParameterOption">
New dashboard parameter
</Radio>
Expand Down Expand Up @@ -205,16 +205,16 @@ export class ParameterMappingInput extends React.Component {
<Input
value={mapTo}
aria-label="Parameter name (key)"
onChange={e => 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 <Select value={mapping.mapTo} onChange={mapTo => this.updateParamMapping({ mapTo })} options={options} />;
return <Select value={mapping.mapTo} onChange={(mapTo) => this.updateParamMapping({ mapTo })} options={options} />;
}

renderStaticValue() {
Expand All @@ -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}
/>
);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -351,7 +352,8 @@ class MappingEditor extends React.Component {
trigger="click"
content={this.renderContent()}
visible={visible}
onVisibleChange={this.onVisibleChange}>
onVisibleChange={this.onVisibleChange}
>
<Button size="small" type="dashed" data-test={`EditParamMappingButton-${mapping.param.name}`}>
<EditOutlinedIcon />
</Button>
Expand All @@ -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 });
};

Expand Down Expand Up @@ -460,7 +462,8 @@ class TitleEditor extends React.Component {
trigger="click"
content={this.renderPopover()}
visible={this.state.showPopup}
onVisibleChange={this.onPopupVisibleChange}>
onVisibleChange={this.onPopupVisibleChange}
>
<Button size="small" type="dashed">
<EditOutlinedIcon />
</Button>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (
<div className="parameters-mapping-list">
Expand All @@ -583,11 +586,11 @@ export class ParameterMappingListInput extends React.Component {
title="Title"
dataIndex="mapping"
key="title"
render={mapping => (
render={(mapping) => (
<TitleEditor
existingParams={existingParams}
mapping={mapping}
onChange={newMapping => this.updateParamMapping(mapping, newMapping)}
onChange={(newMapping) => this.updateParamMapping(mapping, newMapping)}
/>
)}
/>
Expand All @@ -596,19 +599,19 @@ export class ParameterMappingListInput extends React.Component {
dataIndex="mapping"
key="keyword"
className="keyword"
render={mapping => <code>{`{{ ${mapping.name} }}`}</code>}
render={(mapping) => <code>{`{{ ${mapping.name} }}`}</code>}
/>
<Table.Column
title="Default Value"
dataIndex="mapping"
key="value"
render={mapping => this.constructor.getDefaultValue(mapping, this.props.existingParams)}
render={(mapping) => this.constructor.getDefaultValue(mapping, this.props.existingParams)}
/>
<Table.Column
title="Value Source"
dataIndex="mapping"
key="source"
render={mapping => {
render={(mapping) => {
const existingParamsNames = existingParams
.filter(({ type }) => type === mapping.param.type) // exclude mismatching param types
.map(({ name }) => name); // keep names only
Expand Down
Loading

0 comments on commit a69f7fb

Please sign in to comment.