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