client/app/components/EditParameterSettingsDialog.jsx (245 lines of code) (raw):
import { includes, words, capitalize, clone, isNull } from "lodash";
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import Checkbox from "antd/lib/checkbox";
import Modal from "antd/lib/modal";
import Form from "antd/lib/form";
import Button from "antd/lib/button";
import Select from "antd/lib/select";
import Input from "antd/lib/input";
import Divider from "antd/lib/divider";
import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper";
import QuerySelector from "@/components/QuerySelector";
import { Query } from "@/services/query";
import { useUniqueId } from "@/lib/hooks/useUniqueId";
const { Option } = Select;
const formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };
function getDefaultTitle(text) {
return capitalize(words(text).join(" ")); // humanize
}
function isTypeDateRange(type) {
return /-range/.test(type);
}
function joinExampleList(multiValuesOptions) {
const { prefix, suffix } = multiValuesOptions;
return ["value1", "value2", "value3"].map(value => `${prefix}${value}${suffix}`).join(",");
}
function NameInput({ name, type, onChange, existingNames, setValidation }) {
let helpText = "";
let validateStatus = "";
if (!name) {
helpText = "Choose a keyword for this parameter";
setValidation(false);
} else if (includes(existingNames, name)) {
helpText = "Parameter with this name already exists";
setValidation(false);
validateStatus = "error";
} else {
if (isTypeDateRange(type)) {
helpText = (
<React.Fragment>
Appears in query as{" "}
<code style={{ display: "inline-block", color: "inherit" }}>{`{{${name}.start}} {{${name}.end}}`}</code>
</React.Fragment>
);
}
setValidation(true);
}
return (
<Form.Item required label="Keyword" help={helpText} validateStatus={validateStatus} {...formItemProps}>
<Input onChange={e => onChange(e.target.value)} autoFocus />
</Form.Item>
);
}
NameInput.propTypes = {
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
existingNames: PropTypes.arrayOf(PropTypes.string).isRequired,
setValidation: PropTypes.func.isRequired,
type: PropTypes.string.isRequired,
};
function EditParameterSettingsDialog(props) {
const [param, setParam] = useState(clone(props.parameter));
const [isNameValid, setIsNameValid] = useState(true);
const [initialQuery, setInitialQuery] = useState();
const isNew = !props.parameter.name;
// fetch query by id
useEffect(() => {
const queryId = props.parameter.queryId;
if (queryId) {
Query.get({ id: queryId }).then(setInitialQuery);
}
}, [props.parameter.queryId]);
function isFulfilled() {
// name
if (!isNameValid) {
return false;
}
// title
if (param.title === "") {
return false;
}
// query
if (param.type === "query" && !param.queryId) {
return false;
}
return true;
}
function onConfirm() {
// update title to default
if (!param.title) {
// forced to do this cause param won't update in time for save
param.title = getDefaultTitle(param.name);
setParam(param);
}
props.dialog.close(param);
}
const paramFormId = useUniqueId("paramForm");
return (
<Modal
{...props.dialog.props}
title={isNew ? "Add Parameter" : param.name}
width={600}
footer={[
<Button key="cancel" onClick={props.dialog.dismiss}>
Cancel
</Button>,
<Button
key="submit"
htmlType="submit"
disabled={!isFulfilled()}
type="primary"
form={paramFormId}
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 })}
setValidation={setIsNameValid}
existingNames={props.existingParams}
type={param.type}
/>
)}
<Form.Item required label="Title" {...formItemProps}>
<Input
value={isNull(param.title) ? getDefaultTitle(param.name) : param.title}
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">
<Option value="text" data-test="TextParameterTypeOption">
Text
</Option>
<Option value="number" data-test="NumberParameterTypeOption">
Number
</Option>
<Option value="enum">Dropdown List</Option>
<Option value="query">Query Based Dropdown List</Option>
<Option disabled key="dv1">
<Divider className="select-option-divider" />
</Option>
<Option value="date" data-test="DateParameterTypeOption">
Date
</Option>
<Option value="datetime-local" data-test="DateTimeParameterTypeOption">
Date and Time
</Option>
<Option value="datetime-with-seconds">Date and Time (with seconds)</Option>
<Option disabled key="dv2">
<Divider className="select-option-divider" />
</Option>
<Option value="date-range" data-test="DateRangeParameterTypeOption">
Date Range
</Option>
<Option value="datetime-range">Date and Time Range</Option>
<Option value="datetime-range-with-seconds">Date and Time Range (with seconds)</Option>
</Select>
</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 })}
/>
</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 })}
type="select"
/>
</Form.Item>
)}
{(param.type === "enum" || param.type === "query") && (
<Form.Item className="m-b-0" label=" " colon={false} {...formItemProps}>
<Checkbox
defaultChecked={!!param.multiValuesOptions}
onChange={e =>
setParam({
...param,
multiValuesOptions: e.target.checked
? {
prefix: "",
suffix: "",
separator: ",",
}
: null,
})
}
data-test="AllowMultipleValuesCheckbox">
Allow multiple values
</Checkbox>
</Form.Item>
)}
{(param.type === "enum" || param.type === "query") && param.multiValuesOptions && (
<Form.Item
label="Quotation"
help={
<React.Fragment>
Placed in query as: <code>{joinExampleList(param.multiValuesOptions)}</code>
</React.Fragment>
}
{...formItemProps}>
<Select
value={param.multiValuesOptions.prefix}
onChange={quoteOption =>
setParam({
...param,
multiValuesOptions: {
...param.multiValuesOptions,
prefix: quoteOption,
suffix: quoteOption,
},
})
}
data-test="QuotationSelect">
<Option value="">None (default)</Option>
<Option value="'">Single Quotation Mark</Option>
<Option value={'"'} data-test="DoubleQuotationMarkOption">
Double Quotation Mark
</Option>
</Select>
</Form.Item>
)}
</Form>
</Modal>
);
}
EditParameterSettingsDialog.propTypes = {
parameter: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
dialog: DialogPropType.isRequired,
existingParams: PropTypes.arrayOf(PropTypes.string),
};
EditParameterSettingsDialog.defaultProps = {
existingParams: [],
};
export default wrapDialog(EditParameterSettingsDialog);