all files / src/components/ AlteredSliceTag.jsx

100% Statements 68/68
95.92% Branches 47/49
100% Functions 10/10
100% Lines 65/65
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146               25× 25× 25×                   27× 27× 27× 27× 217×   217×     189× 183×     27×         449× 447× 446× 65×   64× 66× 66×   381× 63× 318× 64× 255× 64× 191× 63× 128× 65×   63×     31× 31× 31× 218×               31×       30×                       26×                             27×           25×                      
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/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 && 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('Chart changes')}
        bsSize="large"
        modalBody={this.renderModalBody()}
      />
    );
  }
}
 
AlteredSliceTag.propTypes = propTypes;