Skip to content

Commit

Permalink
[Widget Params] Switched parameter list to table style
Browse files Browse the repository at this point in the history
  • Loading branch information
ranbena committed Feb 3, 2019
1 parent 69e34f0 commit d92f539
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 45 deletions.
4 changes: 4 additions & 0 deletions client/app/assets/less/ant.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
@import '~antd/lib/radio/style/index';
@import '~antd/lib/time-picker/style/index';
@import '~antd/lib/pagination/style/index';
@import '~antd/lib/table/style/index';
@import '~antd/lib/popover/style/index';
@import '~antd/lib/icon/style/index';
@import '~antd/lib/tag/style/index';
@import 'inc/ant-variables';

// Remove bold in labels for Ant checkboxes and radio buttons
Expand Down
236 changes: 206 additions & 30 deletions client/app/components/ParameterMappingInput.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
/* eslint react/no-multi-comp: 0 */

import { extend, map, includes, findIndex, find, fromPairs } from 'lodash';
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import Select from 'antd/lib/select';
import Table from 'antd/lib/table';
import Popover from 'antd/lib/popover';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Tag from 'antd/lib/tag';
import { ParameterValueInput } from '@/components/ParameterValueInput';
import { ParameterMappingType } from '@/services/widget';
import { Parameter } from '@/services/query';

import './ParameterMappingInput.less';

const { Option } = Select;

Expand Down Expand Up @@ -208,18 +216,86 @@ export class ParameterMappingInput extends React.Component {
render() {
const { mapping } = this.props;
return (
<div key={mapping.name} className="row">
<div className="col-xs-5">
<div className="form-control-static">{'{{ ' + mapping.name + ' }}'}</div>
</div>
<div className="col-xs-7">
{this.renderMappingTypeSelector()}
{this.renderInputBlock()}
{this.renderTitleInput()}
</div>
<div key={mapping.name}>
{this.renderMappingTypeSelector()}
{this.renderInputBlock()}
{this.renderTitleInput()}
</div>
);
}
}

class EditMapping 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) {
super(props);
this.state = {
visible: false,
};
}

onVisibleChange = (visible) => {
if (visible) this.show(); else this.hide();
}

get content() {
const { mapping, clientConfig, Query, onChange } = this.props;

return (
<div className="editMapping">
<header>Edit parameter</header>
<ParameterMappingInput
mapping={mapping}
existingParamNames={this.props.existingParamNames}
onChange={newMapping => onChange(mapping, newMapping)}
getContainerElement={() => this.wrapperRef.current}
clientConfig={clientConfig}
Query={Query}
/>
<footer>
<Button onClick={this.hide}>Close</Button>
</footer>
</div>
);
}

show = () => {
this.setState({ visible: true });
}

hide = () => {
this.setState({ visible: false });
}

render() {
return (
<Popover
placement="right"
trigger="click"
content={this.content}
visible={this.state.visible}
onVisibleChange={this.onVisibleChange}
getPopupContainer={this.props.getContainerElement}
>
<Button size="small" type="dashed">
<Icon type="edit" theme="twoTone" />
</Button>
</Popover>
);
}
}

export class ParameterMappingListInput extends React.Component {
Expand All @@ -242,6 +318,51 @@ export class ParameterMappingListInput extends React.Component {
Query: null,
};

constructor(props) {
super(props);
this.wrapperRef = React.createRef();
}

static getStringValue(value) {
// null
if (!value) {
return '';
}

// range
if (value instanceof Object && 'start' in value && 'end' in value) {
return `${value.start} ~ ${value.end}`;
}

// just to be safe, array or object
if (typeof value === 'object') {
return map(value, v => this.getStringValue(v)).join(', ');
}

// rest
return value.toString();
}

static getDefaultValue(mapping, existingParams) {
const { type, mapTo, name } = mapping;
let { param } = mapping;

// if mapped to another param, swap 'em
if (type === MappingType.DashboardMapToExisting && mapTo !== name) {
const mappedTo = find(existingParams, { name: mapTo });
if (mappedTo) { // just being safe
param = mappedTo;
}

// static type is different since it's fed param.normalizedValue
} else if (type === MappingType.StaticValue) {
param = param.clone().setValue(mapping.value);
}

const value = Parameter.getValue(param);
return this.getStringValue(value);
}

updateParamMapping(oldMapping, newMapping) {
const mappings = [...this.props.mappings];
const index = findIndex(mappings, oldMapping);
Expand All @@ -255,28 +376,83 @@ export class ParameterMappingListInput extends React.Component {
}

render() {
const clientConfig = this.props.clientConfig; // eslint-disable-line react/prop-types
const Query = this.props.Query; // eslint-disable-line react/prop-types
const { clientConfig, Query, existingParams } = this.props; // eslint-disable-line react/prop-types
const dataSource = this.props.mappings.map(mapping => ({ mapping }));

return (
<div>
{this.props.mappings.map((mapping, index) => {
const existingParamsNames = this.props.existingParams
.filter(({ type }) => type === mapping.param.type) // exclude mismatching param types
.map(({ name }) => name); // keep names only

return (
<div key={mapping.name} className={(index === 0 ? '' : ' m-t-15')}>
<ParameterMappingInput
mapping={mapping}
existingParamNames={existingParamsNames}
onChange={newMapping => this.updateParamMapping(mapping, newMapping)}
clientConfig={clientConfig}
Query={Query}
/>
</div>
);
})}
<div ref={this.wrapperRef} className="paramMappingList">
<Table
dataSource={dataSource}
size="middle"
pagination={false}
rowKey={(record, idx) => `row${idx}`}
>
<Table.Column
title="Edit"
dataIndex="mapping"
key="edit"
render={(mapping) => {
const existingParamsNames = existingParams
.filter(({ type }) => type === mapping.param.type) // exclude mismatching param types
.map(({ name }) => name); // keep names only

return (
<EditMapping
mapping={mapping}
existingParamNames={existingParamsNames}
onChange={(oldMapping, newMapping) => this.updateParamMapping(oldMapping, newMapping)}
getContainerElement={() => this.wrapperRef.current}
clientConfig={clientConfig}
Query={Query}
/>
);
}}
/>
<Table.Column
title="Title"
dataIndex="mapping"
key="title"
render={mapping => mapping.title || mapping.param.title}
/>
<Table.Column
title="Keyword"
dataIndex="mapping"
key="keyword"
className="keyword"
render={mapping => <code>{`{{ ${mapping.name} }}`}</code>}
/>
<Table.Column
title="Default Value"
dataIndex="mapping"
key="value"
render={mapping => (
this.constructor.getDefaultValue(mapping, this.props.existingParams)
)}
/>
<Table.Column
title="Value Source"
dataIndex="mapping"
key="source"
render={(mapping) => {
switch (mapping.type) {
case MappingType.DashboardAddNew:
case MappingType.DashboardMapToExisting:
return (
<Fragment>
Dashboard parameter{' '}
<Tag className="tag">{mapping.mapTo}</Tag>
</Fragment>
);
case MappingType.WidgetLevel:
return 'Widget parameter';
case MappingType.StaticValue:
return 'Static value';
default:
return ''; // won't happen (typescript-ftw)
}
}}
/>
</Table>
</div>
);
}
Expand Down
43 changes: 43 additions & 0 deletions client/app/components/ParameterMappingInput.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import '~antd/lib/modal/style/index'; // for ant @vars

.paramMappingList {
.keyword {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;

code {
white-space: nowrap; // so the curly braces don't line break
}
}

// Ant <Tag> overrides
.tag {
margin: 0;
pointer-events: none; // unclickable

&:empty {
display: none;
}
}
}

.editMapping {
width: 340px;

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;
}

footer {
border-top: @border-width-base @border-style-base @border-color-split;
padding: 10px 16px 0;
margin: 20px -16px 0;
text-align: right;
}
}
2 changes: 1 addition & 1 deletion client/app/components/dashboards/AddWidgetDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ class AddWidgetDialog extends React.Component {
export default function init(ngModule) {
ngModule.component('addWidgetDialog', {
template: `
<add-widget-dialog-impl
<add-widget-dialog-impl
dashboard="$ctrl.resolve.dashboard"
close="$ctrl.close"
dismiss="$ctrl.dismiss"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ class EditParameterMappingsDialog extends React.Component {
}

render() {
const existingParams = map(
this.props.dashboard.getParametersDefs(),
({ name, type }) => ({ name, type }),
);

return (
<div>
<div className="modal-header">
Expand All @@ -83,7 +78,7 @@ class EditParameterMappingsDialog extends React.Component {
{(this.state.parameterMappings.length > 0) && (
<ParameterMappingListInput
mappings={this.state.parameterMappings}
existingParams={existingParams}
existingParams={this.props.dashboard.getParametersDefs()}
onChange={mappings => this.updateParamMappings(mappings)}
/>
)}
Expand Down
Loading

0 comments on commit d92f539

Please sign in to comment.