From 9cf396599a881349c2e7e09561f310633db1404b Mon Sep 17 00:00:00 2001 From: Gabriel Dutra Date: Mon, 17 Feb 2020 23:32:10 -0300 Subject: [PATCH] Parameter Mapping UI (1/2) --- .../EditParameterSettingsDialog.jsx | 133 ++++++++++++++++-- .../app/components/ParameterMappingEditor.jsx | 36 +++++ .../components/ParameterMappingEditor.less | 37 +++++ .../app/components/ParameterMappingInput.jsx | 22 +-- .../app/components/ParameterMappingInput.less | 41 +----- client/app/components/Parameters.jsx | 3 +- client/app/services/parameters/Parameter.js | 5 + .../parameters/QueryBasedDropdownParameter.js | 7 + 8 files changed, 224 insertions(+), 60 deletions(-) create mode 100644 client/app/components/ParameterMappingEditor.jsx create mode 100644 client/app/components/ParameterMappingEditor.less diff --git a/client/app/components/EditParameterSettingsDialog.jsx b/client/app/components/EditParameterSettingsDialog.jsx index 59673c0ea1..3023a64738 100644 --- a/client/app/components/EditParameterSettingsDialog.jsx +++ b/client/app/components/EditParameterSettingsDialog.jsx @@ -1,16 +1,23 @@ -import { includes, words, capitalize, clone, isNull } from "lodash"; -import React, { useState, useEffect } from "react"; +import { includes, words, capitalize, clone, isNull, keys, values } from "lodash"; +import React, { useState, useEffect, useRef } 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 Icon from "antd/lib/icon"; import Input from "antd/lib/input"; +import Table from "antd/lib/table"; import Divider from "antd/lib/divider"; +import Tooltip from "antd/lib/tooltip"; +import Popover from "antd/lib/popover"; +import Radio from "antd/lib/radio"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import QuerySelector from "@/components/QuerySelector"; +import ParameterMappingEditor from "@/components//ParameterMappingEditor"; import { Query } from "@/services/query"; +import { QueryBasedParameterMappingType } from "@/services/parameters/QueryBasedDropdownParameter"; const { Option } = Select; const formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } }; @@ -66,20 +73,85 @@ NameInput.propTypes = { type: PropTypes.string.isRequired, }; +// TODO: put this component in its own file +function QueryBasedParamMappingEditor({ parameter, mappingType, staticValue, searchAvailable, onChange }) { + const [showPopover, setShowPopover] = useState(false); + + let currentState = "Undefined"; + if (mappingType === QueryBasedParameterMappingType.DROPDOWN_SEARCH) { + currentState = "Dropdown Search"; + } else if (mappingType === QueryBasedParameterMappingType.STATIC) { + currentState = `Static value: ${staticValue}`; + } + return ( + <> + {currentState} + setShowPopover(false)}> +
+ + + + Dropdown Search{" "} + {!searchAvailable && ( + + + + )} + + + Static Value + + + +
+ + } + visible={showPopover} + onVisibleChange={setShowPopover}> + +
+ + ); +} + +QueryBasedParamMappingEditor.propTypes = { + parameter: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + mappingType: PropTypes.oneOf(values(QueryBasedParameterMappingType)), + staticValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + searchAvailable: PropTypes.bool, + onChange: PropTypes.func, +}; + +QueryBasedParamMappingEditor.defaultProps = { + mappingType: QueryBasedParameterMappingType.UNDEFINED, + staticValue: undefined, + searchAvailable: false, + onChance: () => {}, +}; + function EditParameterSettingsDialog(props) { const [param, setParam] = useState(clone(props.parameter)); const [isNameValid, setIsNameValid] = useState(true); - const [initialQuery, setInitialQuery] = useState(); + const [paramQuery, setParamQuery] = useState(); const isNew = !props.parameter.name; // fetch query by id + const initialQueryId = useRef(props.parameter.queryId); useEffect(() => { - const queryId = props.parameter.queryId; - if (queryId) { - Query.get({ id: queryId }).then(setInitialQuery); + if (initialQueryId.current) { + Query.get({ id: initialQueryId.current }).then(setParamQuery); } - }, [props.parameter.queryId]); + }, []); function isFulfilled() { // name @@ -191,12 +263,55 @@ function EditParameterSettingsDialog(props) { {param.type === "query" && ( setParam({ ...param, queryId: q && q.id })} + selectedQuery={paramQuery} + onChange={q => { + if (q) { + setParamQuery(q); + setParam({ ...param, queryId: q.id }); + } + }} type="select" /> )} + {param.type === "query" && paramQuery && paramQuery.hasParameters() && ( + + `row${idx}`}> + mappingParam.getTitle()} /> + {`{{ ${mappingParam.name} }}`}} + /> + { + const mappingProps = { + parameter: mappingParam, + mappingType: QueryBasedParameterMappingType.UNDEFINED, + searchAvailable: !param.searchColumn, + }; + + // TODO: Update backend verification accordingly and avoid this + if (param.searchColumn === mappingParam.name) { + mappingProps.mappingType = QueryBasedParameterMappingType.DROPDOWN_SEARCH; + mappingProps.searchAvailable = true; + } else if (includes(keys(param.staticValues), mappingParam.name)) { + mappingProps.mappingType = QueryBasedParameterMappingType.STATIC; + mappingProps.staticValue = param.staticValues[mappingParam.name]; + } + return ; + }} + /> +
+
+ )} {(param.type === "enum" || param.type === "query") && ( + {header &&
{header}
} + {children} +
+ + +
+ + ); +} + +ParameterMappingEditor.propTypes = { + header: PropTypes.node, + children: PropTypes.node, + saveDisabled: PropTypes.bool, + onSave: PropTypes.func, + onCancel: PropTypes.func, +}; + +ParameterMappingEditor.defaultProps = { + header: null, + children: null, + saveDisabled: false, + onSave: () => {}, + onCancel: () => {}, +}; diff --git a/client/app/components/ParameterMappingEditor.less b/client/app/components/ParameterMappingEditor.less new file mode 100644 index 0000000000..252018aed3 --- /dev/null +++ b/client/app/components/ParameterMappingEditor.less @@ -0,0 +1,37 @@ +@import "~antd/lib/modal/style/index"; // for ant @vars + +.parameter-mapping-editor { + width: 390px; + + .radio { + display: block; + height: 30px; + line-height: 30px; + } + + .form-item { + margin-bottom: 10px; + } + + header { + padding: 0 16px 10px; + margin: 0 -16px 20px; + border-bottom: @border-width-base @border-style-base @border-color-split; + font-size: @font-size-lg; + font-weight: 500; + color: @heading-color; + display: flex; + justify-content: space-between; + } + + footer { + border-top: @border-width-base @border-style-base @border-color-split; + padding: 10px 16px 0; + margin: 0 -16px; + text-align: right; + + button { + margin-left: 8px; + } + } +} diff --git a/client/app/components/ParameterMappingInput.jsx b/client/app/components/ParameterMappingInput.jsx index 1ed41f1b65..dc222a38f7 100644 --- a/client/app/components/ParameterMappingInput.jsx +++ b/client/app/components/ParameterMappingInput.jsx @@ -18,6 +18,7 @@ import ParameterValueInput from "@/components/ParameterValueInput"; import { ParameterMappingType } from "@/services/widget"; import { Parameter, cloneParameter } from "@/services/parameters"; import HelpTrigger from "@/components/HelpTrigger"; +import ParameterMappingEditor from "@/components/ParameterMappingEditor"; import "./ParameterMappingInput.less"; @@ -325,23 +326,22 @@ class MappingEditor extends React.Component { const { mapping, inputError } = this.state; return ( -
-
- Edit Source and Value -
+ + Edit Source and Value + + } + saveDisabled={!!inputError} + onSave={this.save} + onCancel={this.hide}> -
- - -
-
+ ); } diff --git a/client/app/components/ParameterMappingInput.less b/client/app/components/ParameterMappingInput.less index 4cff86ae21..06bf4ee018 100644 --- a/client/app/components/ParameterMappingInput.less +++ b/client/app/components/ParameterMappingInput.less @@ -1,4 +1,4 @@ -@import '~antd/lib/modal/style/index'; // for ant @vars +@import "~antd/lib/modal/style/index"; // for ant @vars .parameters-mapping-list { .keyword { @@ -22,48 +22,13 @@ } } -.parameter-mapping-editor { - width: 390px; - - .radio { - display: block; - height: 30px; - line-height: 30px; - } - - .form-item { - margin-bottom: 10px; - } - - header { - padding: 0 16px 10px; - margin: 0 -16px 20px; - border-bottom: @border-width-base @border-style-base @border-color-split; - font-size: @font-size-lg; - font-weight: 500; - color: @heading-color; - display: flex; - justify-content: space-between; - } - - footer { - border-top: @border-width-base @border-style-base @border-color-split; - padding: 10px 16px 0; - margin: 0 -16px; - text-align: right; - - button { - margin-left: 8px; - } - } -} - .parameter-mapping-title { .text { margin-right: 3px; } - &.disabled, .fa { + &.disabled, + .fa { color: #a4a4a4; } diff --git a/client/app/components/Parameters.jsx b/client/app/components/Parameters.jsx index 075033de25..8ae3fd0c98 100644 --- a/client/app/components/Parameters.jsx +++ b/client/app/components/Parameters.jsx @@ -7,7 +7,6 @@ import { Parameter, createParameter } from "@/services/parameters"; import ParameterApplyButton from "@/components/ParameterApplyButton"; import ParameterValueInput from "@/components/ParameterValueInput"; import EditParameterSettingsDialog from "./EditParameterSettingsDialog"; -import { toHuman } from "@/lib/utils"; import "./Parameters.less"; @@ -123,7 +122,7 @@ export default class Parameters extends React.Component { return (
- + {editable && (