From de99025f495c7308d0ec75741ae610c1eb216cb1 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 10 Nov 2018 11:33:58 -0500 Subject: [PATCH] Convert GenericDoughnutChart to use WithAPIData Signed-off-by: Mcat12 --- .../dashboard/GenericDoughnutChart.js | 160 ++++++++++-------- .../dashboard/GenericDoughnutChart.test.js | 49 ++---- 2 files changed, 101 insertions(+), 108 deletions(-) diff --git a/src/components/dashboard/GenericDoughnutChart.js b/src/components/dashboard/GenericDoughnutChart.js index bd8ba39..072e7a8 100644 --- a/src/components/dashboard/GenericDoughnutChart.js +++ b/src/components/dashboard/GenericDoughnutChart.js @@ -11,14 +11,15 @@ import React, { Component } from "react"; import { Doughnut } from "react-chartjs-2"; import PropTypes from "prop-types"; -import { makeCancelable, ignoreCancel } from "../../util"; +import { WithAPIData } from "../common/WithAPIData"; -class GenericDoughnutChart extends Component { - state = { - loading: true, - data: [], - colors: [], - labels: [] +export class GenericDoughnutChart extends Component { + static propTypes = { + title: PropTypes.string.isRequired, + loading: PropTypes.bool.isRequired, + data: PropTypes.array.isRequired, + colors: PropTypes.array.isRequired, + labels: PropTypes.array.isRequired }; constructor(props) { @@ -26,54 +27,6 @@ class GenericDoughnutChart extends Component { this.chartRef = React.createRef(); } - updateChart = () => { - this.updateHandler = makeCancelable(this.props.apiCall(), { - repeat: this.updateChart, - interval: 10 * 60 * 1000 - }); - this.updateHandler.promise - .then(apiData => { - const colors = [ - "#20a8d8", - "#f86c6b", - "#4dbd74", - "#f8cb00", - "#263238", - "#63c2de", - "#b0bec5" - ]; - const data = []; - const labels = []; - const usedColors = []; - - // Fill in dataset metadata - let i = 0; - for (let entry of apiData) { - data.push(entry.percent); - labels.push(entry.name.length !== 0 ? entry.name : entry.ip); - usedColors.push( - // If we ran out of colors, make a random one - i < colors.length - ? colors[i] - : "#" + - parseInt("" + Math.random() * 0xffffff, 10) - .toString(16) - .padStart(6, "0") - ); - - i++; - } - - this.setState({ - loading: false, - data, - colors: usedColors, - labels - }); - }) - .catch(ignoreCancel); - }; - handleClick = (e, index) => { // Hide the entry by clicking on the internal legend item const chart = this.chartRef.current.chartInstance; @@ -87,14 +40,6 @@ class GenericDoughnutChart extends Component { this.forceUpdate(); }; - componentDidMount() { - this.updateChart(); - } - - componentWillUnmount() { - this.updateHandler.cancel(); - } - render() { const options = { legend: { display: false }, @@ -119,7 +64,7 @@ class GenericDoughnutChart extends Component { const meta = this.chartRef.current !== null ? this.chartRef.current.chartInstance.getDatasetMeta(0).data - : this.state.data.map(() => ({ hidden: false })); + : this.props.data.map(() => ({ hidden: false })); return (
@@ -134,19 +79,19 @@ class GenericDoughnutChart extends Component { data={{ datasets: [ { - data: this.state.data, - backgroundColor: this.state.colors + data: this.props.data, + backgroundColor: this.props.colors } ], - labels: this.state.labels + labels: this.props.labels }} />
    - {this.state.labels + {this.props.labels // Zip label and color together - .map((label, i) => [label, this.state.colors[i]]) + .map((label, i) => [label, this.props.colors[i]]) // Create the list items .map(([label, color], i) => (
- {this.state.loading ? ( + {this.props.loading ? (
{ + const colors = [ + "#20a8d8", + "#f86c6b", + "#4dbd74", + "#f8cb00", + "#263238", + "#63c2de", + "#b0bec5" + ]; + const data = []; + const labels = []; + const usedColors = []; + + // Fill in dataset metadata + let i = 0; + for (let entry of apiData) { + data.push(entry.percent); + labels.push(entry.name.length !== 0 ? entry.name : entry.ip); + usedColors.push( + // If we ran out of colors, make a random one + i < colors.length + ? colors[i] + : "#" + + parseInt("" + Math.random() * 0xffffff, 10) + .toString(16) + .padStart(6, "0") + ); + + i++; + } + + return { + loading: false, + data, + colors: usedColors, + labels + }; }; -export default GenericDoughnutChart; +/** + * The props used to show a loading state (either initial load or error) + * + * @returns {*} the loading props + */ +export const loadingProps = () => ({ + loading: true, + data: [], + colors: [], + labels: [] +}); + +export default ({ apiCall, title, ...props }) => ( + ( + + )} + renderOk={data => ( + + )} + renderErr={() => ( + + )} + /> +); diff --git a/src/components/dashboard/GenericDoughnutChart.test.js b/src/components/dashboard/GenericDoughnutChart.test.js index 1870e6c..3b79612 100644 --- a/src/components/dashboard/GenericDoughnutChart.test.js +++ b/src/components/dashboard/GenericDoughnutChart.test.js @@ -10,7 +10,11 @@ import React from "react"; import { shallow } from "enzyme"; -import GenericDoughnutChart from "./GenericDoughnutChart"; +import { + GenericDoughnutChart, + transformData, + loadingProps +} from "./GenericDoughnutChart"; const fakeData = [ { name: "roberta.net", ip: "8.239.48.32", percent: 0.38411761010240625 }, @@ -18,59 +22,36 @@ const fakeData = [ { name: "christop.net", ip: "181.219.42.222", percent: 0.6249293208519193 } ]; -it("shows loading indicator before first load", () => { +it("shows loading indicator correctly", () => { const wrapper = shallow( - + ); - expect(wrapper.state().loading).toBeTruthy(); expect(wrapper.children(".card-img-overlay")).toExist(); }); -it("hides loading indicator after first load", async () => { +it("hides loading indicator correctly", async () => { const wrapper = shallow( - Promise.resolve(fakeData)} - /> + ); - await tick(); - wrapper.update(); - - expect(wrapper.state().loading).toBeFalsy(); expect(wrapper.children(".card-img-overlay")).not.toExist(); }); it("loads API data correctly", async () => { - const wrapper = shallow( - Promise.resolve(fakeData)} - /> - ); + const data = transformData(fakeData); - await tick(); - wrapper.update(); - - expect(wrapper.state().colors).toEqual(["#20a8d8", "#f86c6b", "#4dbd74"]); - expect(wrapper.state().labels[0]).toEqual(fakeData[0].name); - expect(wrapper.state().labels[1]).toEqual(fakeData[1].ip); - expect(wrapper.state().data).toEqual(fakeData.map(entry => entry.percent)); + expect(data.colors).toEqual(["#20a8d8", "#f86c6b", "#4dbd74"]); + expect(data.labels[0]).toEqual(fakeData[0].name); + expect(data.labels[1]).toEqual(fakeData[1].ip); + expect(data.data).toEqual(fakeData.map(entry => entry.percent)); }); it("displays the title", () => { const title = "title"; const wrapper = shallow( - + ); expect(wrapper.find(".card-header")).toHaveText(title); }); - -it("calls the API callback", () => { - const apiCall = jest.fn(ignoreAPI); - shallow(); - - expect(apiCall).toHaveBeenCalled(); -});