From 18bac844d8413b0c0be254c662ad71cf1864b109 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Sat, 9 Feb 2019 13:39:21 +0200 Subject: [PATCH 1/4] Alternative implementation of dashboard param title editing --- .../app/components/ParameterMappingInput.jsx | 104 +++++++++++------- .../components/dashboards/AddWidgetDialog.jsx | 14 ++- .../EditParameterMappingsDialog.jsx | 10 +- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/client/app/components/ParameterMappingInput.jsx b/client/app/components/ParameterMappingInput.jsx index 6457627ce7..b912732abf 100644 --- a/client/app/components/ParameterMappingInput.jsx +++ b/client/app/components/ParameterMappingInput.jsx @@ -1,9 +1,8 @@ /* eslint react/no-multi-comp: 0 */ -import { extend, map, includes, findIndex, find, fromPairs, clone, isEmpty, replace } from 'lodash'; +import { extend, each, map, includes, findIndex, find, fromPairs, clone, isEmpty, replace } from 'lodash'; import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; import Select from 'antd/lib/select'; import Table from 'antd/lib/table'; import Popover from 'antd/lib/popover'; @@ -89,6 +88,34 @@ export function editableMappingsToParameterMappings(mappings) { )); } +export function synchronizeWidgetTitles(sourceWidget, widgets) { + const affectedWidgets = []; + + const sourceMappings = sourceWidget.options.parameterMappings; + each(sourceMappings, (sourceMapping) => { + if (sourceMapping.type === ParameterMappingType.DashboardLevel) { + each(widgets, (widget) => { + const widgetMappings = widget.options.parameterMappings; + each(widgetMappings, (widgetMapping) => { + // check if mapped to the same dashboard-level parameter + if ( + (widgetMapping.type === ParameterMappingType.DashboardLevel) && + (widgetMapping.mapTo === sourceMapping.mapTo) + ) { + // dirty check - update only when needed + if (widgetMapping.title !== sourceMapping.title) { + widgetMapping.title = sourceMapping.title; + affectedWidgets.push(widget); + } + } + }); + }); + } + }); + + return affectedWidgets; +} + export class ParameterMappingInput extends React.Component { static propTypes = { mapping: PropTypes.object, // eslint-disable-line react/forbid-prop-types @@ -132,13 +159,13 @@ export class ParameterMappingInput extends React.Component { } this.updateParamMapping({ type, mapTo }); - } + }; updateParamMapping = (update) => { const { onChange, mapping } = this.props; const newMapping = extend({}, mapping, update); onChange(newMapping); - } + }; renderMappingTypeSelector() { const noExisting = isEmpty(this.props.existingParamNames); @@ -274,7 +301,7 @@ class EditMapping extends React.Component { static defaultProps = { clientConfig: null, Query: null, - } + }; constructor(props) { super(props); @@ -287,7 +314,7 @@ class EditMapping extends React.Component { onVisibleChange = (visible) => { if (visible) this.show(); else this.hide(); - } + }; onChange = (mapping) => { let inputError = null; @@ -301,7 +328,7 @@ class EditMapping extends React.Component { } this.setState({ mapping, inputError }); - } + }; get content() { const { mapping, inputError } = this.state; @@ -339,18 +366,18 @@ class EditMapping extends React.Component { save = () => { this.props.onChange(this.props.mapping, this.state.mapping); this.hide(); - } + }; show = () => { this.setState({ visible: true, mapping: clone(this.props.mapping), // restore original state }); - } + }; hide = () => { this.setState({ visible: false }); - } + }; render() { return ( @@ -376,27 +403,28 @@ class Title extends React.Component { mapping: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types onChange: PropTypes.func.isRequired, getContainerElement: PropTypes.func.isRequired, - } + }; static defaultProps = { existingParams: [], - } + }; state = { showPopup: false, - title: this.props.mapping.title, - } + title: this.defaultTitleValue, + editingTitle: '', // will be set on editing + }; onPopupVisibleChange = (showPopup) => { - this.setState({ + this.setState(({ title }) => ({ showPopup, - title: this.props.mapping.title, // reset title - }); - } + editingTitle: title, + })); + }; - onTitleChange = (event) => { - this.setState({ title: event.target.value }); - } + onEditingTitleChange = (event) => { + this.setState({ editingTitle: event.target.value }); + }; get popover() { const { param: { title: paramTitle } } = this.props.mapping; @@ -405,9 +433,9 @@ class Title extends React.Component {
{ - const newMapping = extend({}, this.props.mapping, { title: this.state.title }); - this.props.onChange(newMapping); - this.hide(); - } + this.setState(({ editingTitle }) => ({ + title: editingTitle, + }), () => { + const newMapping = extend({}, this.props.mapping, { title: this.state.title }); + this.props.onChange(newMapping); + this.hide(); + }); + }; hide = () => { this.setState({ showPopup: false }); - } + }; renderEditButton() { return ( @@ -489,11 +521,6 @@ class Title extends React.Component { 'Titles for static values don\'t appear in widgets', , ]; - } else if (this.isTypeMappedToOther) { // mapped to other param - [content, icon] = [ - `This title is taken from parameter "${this.props.mapping.mapTo}"`, - , - ]; } else { return null; } @@ -510,13 +537,10 @@ class Title extends React.Component { } render() { - // static value and mapped types are non-editable hence disabled - const disabled = this.isTypeMappedToOther || this.isTypeStatic; - return ( -
- {this.titleValue} - {disabled ? this.renderTooltip() : this.renderEditButton()} +
+ {this.state.title} + {this.renderEditButton()}
); } diff --git a/client/app/components/dashboards/AddWidgetDialog.jsx b/client/app/components/dashboards/AddWidgetDialog.jsx index 7d2104d7e4..1d8cf82f5e 100644 --- a/client/app/components/dashboards/AddWidgetDialog.jsx +++ b/client/app/components/dashboards/AddWidgetDialog.jsx @@ -10,6 +10,7 @@ import { MappingType, ParameterMappingListInput, editableMappingsToParameterMappings, + synchronizeWidgetTitles, } from '@/components/ParameterMappingInput'; import { QueryTagsControl } from '@/components/tags-control/TagsControl'; @@ -150,8 +151,12 @@ class AddWidgetDialog extends React.Component { widget.options.position.col = position.col; widget.options.position.row = position.row; - widget - .save() + const widgetsToSave = [ + widget, + ...synchronizeWidgetTitles(widget, dashboard.widgets), + ]; + + Promise.all(map(widgetsToSave, w => w.save())) .then(() => { dashboard.widgets.push(widget); this.props.dialog.close(); @@ -284,10 +289,7 @@ class AddWidgetDialog extends React.Component { } render() { - const existingParams = map( - this.props.dashboard.getParametersDefs(), - ({ name, type }) => ({ name, type }), - ); + const existingParams = this.props.dashboard.getParametersDefs(); const { dialog } = this.props; return ( diff --git a/client/app/components/dashboards/EditParameterMappingsDialog.jsx b/client/app/components/dashboards/EditParameterMappingsDialog.jsx index 8c485b8027..580f6173c8 100644 --- a/client/app/components/dashboards/EditParameterMappingsDialog.jsx +++ b/client/app/components/dashboards/EditParameterMappingsDialog.jsx @@ -7,6 +7,7 @@ import { ParameterMappingListInput, parameterMappingsToEditableMappings, editableMappingsToParameterMappings, + synchronizeWidgetTitles, } from '@/components/ParameterMappingInput'; class EditParameterMappingsDialog extends React.Component { @@ -35,8 +36,13 @@ class EditParameterMappingsDialog extends React.Component { this.setState({ saveInProgress: true }); widget.options.parameterMappings = editableMappingsToParameterMappings(this.state.parameterMappings); - widget - .save() + + const widgetsToSave = [ + widget, + ...synchronizeWidgetTitles(widget, this.props.dashboard.widgets), + ]; + + Promise.all(map(widgetsToSave, w => w.save())) .then(() => { this.props.dialog.close(); }) From 88d895a61f329a1602910045d753f1c40f75e720 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Mon, 11 Feb 2019 14:47:43 +0200 Subject: [PATCH 2/4] Improve implementation --- .../app/components/ParameterMappingInput.jsx | 200 ++++++------------ .../app/components/ParameterMappingInput.less | 10 +- .../components/dashboards/AddWidgetDialog.jsx | 2 +- .../EditParameterMappingsDialog.jsx | 2 +- client/app/services/dashboard.js | 7 - 5 files changed, 77 insertions(+), 144 deletions(-) diff --git a/client/app/components/ParameterMappingInput.jsx b/client/app/components/ParameterMappingInput.jsx index b912732abf..ed0196291c 100644 --- a/client/app/components/ParameterMappingInput.jsx +++ b/client/app/components/ParameterMappingInput.jsx @@ -1,8 +1,9 @@ -/* eslint react/no-multi-comp: 0 */ +/* eslint-disable react/no-multi-comp */ -import { extend, each, map, includes, findIndex, find, fromPairs, clone, isEmpty, replace } from 'lodash'; +import { isString, extend, each, map, includes, findIndex, find, fromPairs, clone, isEmpty, replace } from 'lodash'; import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import Select from 'antd/lib/select'; import Table from 'antd/lib/table'; import Popover from 'antd/lib/popover'; @@ -15,8 +16,8 @@ import Form from 'antd/lib/form'; import Tooltip from 'antd/lib/tooltip'; import { ParameterValueInput } from '@/components/ParameterValueInput'; import { ParameterMappingType } from '@/services/widget'; -import { Parameter } from '@/services/query'; -import { IS_DASHBOARD_PARAM_SOURCE } from '@/services/dashboard'; +import { clientConfig } from '@/services/auth'; +import { Query, Parameter } from '@/services/query'; import './ParameterMappingInput.less'; @@ -88,10 +89,9 @@ export function editableMappingsToParameterMappings(mappings) { )); } -export function synchronizeWidgetTitles(sourceWidget, widgets) { +export function synchronizeWidgetTitles(sourceMappings, widgets) { const affectedWidgets = []; - const sourceMappings = sourceWidget.options.parameterMappings; each(sourceMappings, (sourceMapping) => { if (sourceMapping.type === ParameterMappingType.DashboardLevel) { each(widgets, (widget) => { @@ -135,15 +135,11 @@ export class ParameterMappingInput extends React.Component { inputError: null, }; - constructor(props) { - super(props); - - this.formItemProps = { - labelCol: { span: 5 }, - wrapperCol: { span: 16 }, - className: 'formItem', - }; - } + formItemProps = { + labelCol: { span: 5 }, + wrapperCol: { span: 16 }, + className: 'form-item', + }; updateSourceType = (type) => { let { mapping: { mapTo } } = this.props; @@ -288,19 +284,11 @@ export class ParameterMappingInput extends React.Component { } } -class EditMapping extends React.Component { +class MappingEditor extends React.Component { static propTypes = { mapping: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types existingParamNames: PropTypes.arrayOf(PropTypes.string).isRequired, onChange: PropTypes.func.isRequired, - getContainerElement: PropTypes.func.isRequired, - clientConfig: PropTypes.any, // eslint-disable-line react/forbid-prop-types - Query: PropTypes.any, // eslint-disable-line react/forbid-prop-types - }; - - static defaultProps = { - clientConfig: null, - Query: null, }; constructor(props) { @@ -330,13 +318,12 @@ class EditMapping extends React.Component { this.setState({ mapping, inputError }); }; - get content() { + getContent() { const { mapping, inputError } = this.state; - const { clientConfig, Query } = this.props; const helpUrl = replace(HELP_URL, '{0}', 'edit_mapping'); return ( -
+
Edit Source and Value {/* eslint-disable-next-line react/jsx-no-target-blank */} @@ -350,7 +337,6 @@ class EditMapping extends React.Component { mapping={mapping} existingParamNames={this.props.existingParamNames} onChange={this.onChange} - getContainerElement={() => this.wrapperRef.current} clientConfig={clientConfig} Query={Query} inputError={inputError} @@ -384,10 +370,9 @@ class EditMapping extends React.Component {