Skip to content

Commit

Permalink
[Explore] Altered Slice Tag (#3668)
Browse files Browse the repository at this point in the history
* Added altered tag to explore slice view and fixes #3616

* unit tests

* Moved getDiffs logic into AlteredSliceTag

* code style fixs
  • Loading branch information
Mogball authored and mistercrunch committed Nov 11, 2017
1 parent 83e6807 commit 4d48d5d
Show file tree
Hide file tree
Showing 6 changed files with 510 additions and 4 deletions.
145 changes: 145 additions & 0 deletions superset/assets/javascripts/components/AlteredSliceTag.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Table, Tr, Td, Thead, Th } from 'reactable';
import { isEqual, isEmpty } from 'underscore';

import TooltipWrapper from './TooltipWrapper';
import { controls } from '../explore/stores/controls';
import ModalTrigger from './ModalTrigger';
import { t } from '../locales';

const propTypes = {
origFormData: PropTypes.object.isRequired,
currentFormData: PropTypes.object.isRequired,
};

export default class AlteredSliceTag extends React.Component {

constructor(props) {
super(props);
const diffs = this.getDiffs(props);
this.state = { diffs, hasDiffs: !isEmpty(diffs) };
}

componentWillReceiveProps(newProps) {
// Update differences if need be
if (isEqual(this.props, newProps)) {
return;
}
const diffs = this.getDiffs(newProps);
this.setState({ diffs, hasDiffs: !isEmpty(diffs) });
}

getDiffs(props) {
// Returns all properties that differ in the
// current form data and the saved form data
const ofd = props.origFormData;
const cfd = props.currentFormData;
const fdKeys = Object.keys(cfd);
const diffs = {};
for (const fdKey of fdKeys) {
// Ignore values that are undefined/nonexisting in either
if (!ofd[fdKey] && !cfd[fdKey]) {
continue;
}
if (!isEqual(ofd[fdKey], cfd[fdKey])) {
diffs[fdKey] = { before: ofd[fdKey], after: cfd[fdKey] };
}
}
return diffs;
}

formatValue(value, key) {
// Format display value based on the control type
// or the value type
if (value === undefined) {
return 'N/A';
} else if (value === null) {
return 'null';
} else if (controls[key] && controls[key].type === 'FilterControl') {
if (!value.length) {
return '[]';
}
return value.map((v) => {
const filterVal = v.val.constructor === Array ? `[${v.val.join(', ')}]` : v.val;
return `${v.col} ${v.op} ${filterVal}`;
}).join(', ');
} else if (controls[key] && controls[key].type === 'BoundsControl') {
return `Min: ${value[0]}, Max: ${value[1]}`;
} else if (controls[key] && controls[key].type === 'CollectionControl') {
return value.map(v => JSON.stringify(v)).join(', ');
} else if (typeof value === 'boolean') {
return value ? 'true' : 'false';
} else if (value.constructor === Array) {
return value.length ? value.join(', ') : '[]';
} else if (typeof value === 'string' || typeof value === 'number') {
return value;
}
return JSON.stringify(value);
}

renderRows() {
const diffs = this.state.diffs;
const rows = [];
for (const key in diffs) {
rows.push(
<Tr key={key}>
<Td column="control" data={(controls[key] && controls[key].label) || key} />
<Td column="before">{this.formatValue(diffs[key].before, key)}</Td>
<Td column="after">{this.formatValue(diffs[key].after, key)}</Td>
</Tr>,
);
}
return rows;
}

renderModalBody() {
return (
<Table className="table" sortable>
<Thead>
<Th column="control">Control</Th>
<Th column="before">Before</Th>
<Th column="after">After</Th>
</Thead>
{this.renderRows()}
</Table>
);
}

renderTriggerNode() {
return (
<TooltipWrapper
label="difference"
tooltip={t('Click to see difference')}
>
<span
className="label label-warning m-l-5"
style={{ fontSize: '12px' }}
>
{t('Altered')}
</span>
</TooltipWrapper>
);
}

render() {
// Return nothing if there are no differences
if (!this.state.hasDiffs) {
return null;
}
// Render the label-warning 'Altered' tag which the user may
// click to open a modal containing a table summarizing the
// differences in the slice
return (
<ModalTrigger
animation
triggerNode={this.renderTriggerNode()}
modalTitle={t('Slice changes')}
bsSize="large"
modalBody={this.renderModalBody()}
/>
);
}
}

AlteredSliceTag.propTypes = propTypes;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { chartPropType } from '../../chart/chartReducer';
import ExploreActionButtons from './ExploreActionButtons';
import EditableTitle from '../../components/EditableTitle';
import AlteredSliceTag from '../../components/AlteredSliceTag';
import FaveStar from '../../components/FaveStar';
import TooltipWrapper from '../../components/TooltipWrapper';
import Timer from '../../components/Timer';
Expand Down Expand Up @@ -54,6 +55,13 @@ class ExploreChartHeader extends React.PureComponent {
});
}

renderAlteredTag() {
const origFormData = (this.props.slice && this.props.slice.form_data) || {};
const currentFormData = this.props.form_data;
const tagProps = { origFormData, currentFormData };
return (<AlteredSliceTag {...tagProps} />);
}

renderChartTitle() {
let title;
if (this.props.slice) {
Expand Down Expand Up @@ -106,6 +114,8 @@ class ExploreChartHeader extends React.PureComponent {
</span>
}

{this.renderAlteredTag()}

<div className="pull-right">
{this.props.chart.chartStatus === 'success' &&
queryResponse &&
Expand Down
1 change: 1 addition & 0 deletions superset/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"srcdoc-polyfill": "^1.0.0",
"supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40",
"urijs": "^1.18.10",
"underscore": "^1.8.3",
"viewport-mercator-project": "^2.1.0"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 4d48d5d

Please sign in to comment.