Completed
Push — master ( 05deb1...eb65e9 )
by Alejandro
15s queued 11s
created

SortableBarGraph.determineStats   A

Complexity

Conditions 2

Size

Total Lines 24
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0054

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 24
ccs 8
cts 9
cp 0.8889
rs 9.376
c 0
b 0
f 0
cc 2
crap 2.0054
1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import { fromPairs, head, keys, pipe, prop, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda';
4
import SortingDropdown from '../utils/SortingDropdown';
5
import PaginationDropdown from '../utils/PaginationDropdown';
6
import { rangeOf } from '../utils/utils';
7
import { roundTen } from '../utils/helpers/numbers';
8
import SimplePaginator from '../common/SimplePaginator';
9
import GraphCard from './GraphCard';
10
11 2
const { max } = Math;
12 8
const toLowerIfString = (value) => type(value) === 'String' ? toLower(value) : value;
13 1298
const pickKeyFromPair = ([ key ]) => key;
14 320
const pickValueFromPair = ([ , value ]) => value;
15
16
export default class SortableBarGraph extends React.Component {
17 2
  static propTypes = {
18
    stats: PropTypes.object.isRequired,
19
    highlightedStats: PropTypes.object,
20
    title: PropTypes.string.isRequired,
21
    sortingItems: PropTypes.object.isRequired,
22
    extraHeaderContent: PropTypes.func,
23
    withPagination: PropTypes.bool,
24
    onClick: PropTypes.func,
25
  };
26
27 9
  state = {
28
    orderField: undefined,
29
    orderDir: undefined,
30
    currentPage: 1,
31
    itemsPerPage: Infinity,
32
  };
33
34
  getSortedPairsForStats(stats, sortingItems) {
35 17
    const pairs = toPairs(stats);
36 17
    const sortedPairs = !this.state.orderField ? pairs : sortBy(
37
      pipe(
38
        prop(this.state.orderField === head(keys(sortingItems)) ? 0 : 1),
39
        toLowerIfString
40
      ),
41
      pairs
42
    );
43
44 17
    return !this.state.orderDir || this.state.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs);
45
  }
46
47
  determineStats(stats, highlightedStats, sortingItems) {
48 17
    const sortedPairs = this.getSortedPairsForStats(stats, sortingItems);
49 17
    const sortedKeys = sortedPairs.map(pickKeyFromPair);
50
    // The highlighted stats have to be ordered based on the regular stats, not on its own values
51 17
    const sortedHighlightedPairs = highlightedStats && toPairs(
52
      { ...zipObj(sortedKeys, sortedKeys.map(() => 0)), ...highlightedStats }
53
    );
54
55 17
    if (sortedPairs.length <= this.state.itemsPerPage) {
56 15
      return {
57
        currentPageStats: fromPairs(sortedPairs),
58
        currentPageHighlightedStats: sortedHighlightedPairs && fromPairs(sortedHighlightedPairs),
59
      };
60
    }
61
62 2
    const pages = splitEvery(this.state.itemsPerPage, sortedPairs);
63 2
    const highlightedPages = sortedHighlightedPairs && splitEvery(this.state.itemsPerPage, sortedHighlightedPairs);
64
65 2
    return {
66
      currentPageStats: fromPairs(this.determineCurrentPagePairs(pages)),
67
      currentPageHighlightedStats: highlightedPages && fromPairs(this.determineCurrentPagePairs(highlightedPages)),
68
      pagination: this.renderPagination(pages.length),
69
      max: roundTen(max(...sortedPairs.map(pickValueFromPair))),
70
    };
71
  }
72
73
  determineCurrentPagePairs(pages) {
74 2
    const page = pages[this.state.currentPage - 1];
75
76 2
    if (this.state.currentPage < pages.length) {
77 2
      return page;
78
    }
79
80
    const firstPageLength = pages[0].length;
81
82
    // Using the "hidden" key, the chart will just replace the label by an empty string
83
    return [ ...page, ...rangeOf(firstPageLength - page.length, (i) => [ `hidden_${i}`, 0 ]) ];
84
  }
85
86
  renderPagination(pagesCount) {
87 2
    const { currentPage } = this.state;
88 2
    const setCurrentPage = (currentPage) => this.setState({ currentPage });
89
90 2
    return <SimplePaginator currentPage={currentPage} pagesCount={pagesCount} setCurrentPage={setCurrentPage} />;
91
  }
92
93
  render() {
94
    const {
95
      stats,
96
      highlightedStats,
97
      sortingItems,
98
      title,
99
      extraHeaderContent,
100
      withPagination = true,
101
      ...rest
102 17
    } = this.props;
103 17
    const { currentPageStats, currentPageHighlightedStats, pagination, max } = this.determineStats(
104
      stats,
105
      highlightedStats && keys(highlightedStats).length > 0 ? highlightedStats : undefined,
106
      sortingItems
107
    );
108 17
    const activeCities = keys(currentPageStats);
109 17
    const computeTitle = () => (
110 8
      <React.Fragment>
111
        {title}
112
        <div className="float-right">
113
          <SortingDropdown
114
            isButton={false}
115
            right
116
            items={sortingItems}
117
            orderField={this.state.orderField}
118
            orderDir={this.state.orderDir}
119 4
            onChange={(orderField, orderDir) => this.setState({ orderField, orderDir, currentPage: 1 })}
120
          />
121
        </div>
122
        {withPagination && keys(stats).length > 50 && (
123
          <div className="float-right">
124
            <PaginationDropdown
125
              toggleClassName="btn-sm paddingless mr-3"
126
              ranges={[ 50, 100, 200, 500 ]}
127
              value={this.state.itemsPerPage}
128 4
              setValue={(itemsPerPage) => this.setState({ itemsPerPage, currentPage: 1 })}
129
            />
130
          </div>
131
        )}
132
        {extraHeaderContent && (
133
          <div className="float-right">
134
            {extraHeaderContent(pagination ? activeCities : undefined)}
135
          </div>
136
        )}
137
      </React.Fragment>
138
    );
139
140 17
    return (
141
      <GraphCard
142
        isBarChart
143
        title={computeTitle}
144
        stats={currentPageStats}
145
        highlightedStats={currentPageHighlightedStats}
146
        footer={pagination}
147
        max={max}
148
        {...rest}
149
      />
150
    );
151
  }
152
}
153