From 45c6e137f85f8add19bea88726bdc3083696f858 Mon Sep 17 00:00:00 2001 From: Michael Levin Date: Tue, 14 Jan 2025 11:41:08 -0500 Subject: [PATCH 1/2] [Feature] Add time filter to devices chart --- .../dashboard_content/DevicesChart.js | 85 +++++++++++++------ .../__tests__/DevicesChart.spec.js | 12 +-- .../DeviceDemographics.spec.js.snap | 22 ++++- .../__snapshots__/DevicesChart.spec.js.snap | 74 ++++++++++++++-- js/components/select/FilterSelect.js | 58 +++++++++++++ .../agency_select/AgencySelect.js | 0 .../__tests__/AgencySelect.spec.js | 0 .../__snapshots__/AgencySelect.spec.js.snap | 0 .../agency_select/__tests__/index.spec.js | 0 .../{ => select}/agency_select/index.js | 0 js/lib/react_setup.js | 2 +- 11 files changed, 208 insertions(+), 45 deletions(-) create mode 100644 js/components/select/FilterSelect.js rename js/components/{ => select}/agency_select/AgencySelect.js (100%) rename js/components/{ => select}/agency_select/__tests__/AgencySelect.spec.js (100%) rename js/components/{ => select}/agency_select/__tests__/__snapshots__/AgencySelect.spec.js.snap (100%) rename js/components/{ => select}/agency_select/__tests__/index.spec.js (100%) rename js/components/{ => select}/agency_select/index.js (100%) diff --git a/js/components/dashboard_content/DevicesChart.js b/js/components/dashboard_content/DevicesChart.js index c34df0a3..8c8e1ff9 100644 --- a/js/components/dashboard_content/DevicesChart.js +++ b/js/components/dashboard_content/DevicesChart.js @@ -7,6 +7,7 @@ import barChart from "../../lib/chart_helpers/barchart"; import formatters from "../../lib/chart_helpers/formatters"; import transformers from "../../lib/chart_helpers/transformers"; import Tooltip from "../tooltip/Tooltip"; +import FilterSelect from "../select/FilterSelect"; /** * Retrieves the devices report from the passed data URL and creates a @@ -20,40 +21,59 @@ import Tooltip from "../tooltip/Tooltip"; * @returns {import('react').ReactElement} The rendered element */ function DevicesChart({ dataHrefBase }) { - const jsonDataURL = `${dataHrefBase}/devices.json`; - const csvDataURL = `${dataHrefBase}/devices.csv`; + const chartBuilder = new ChartBuilder(); + const reportFilters = [ + ["30 Days", "devices"], + ["7 Days", "devices-7-days"], + ["90 Days", "devices-90-days"], + ]; + const [dataFileName, setDataFileName] = useState("devices"); const ref = useRef(null); - const [deviceData, setDeviceData] = useState(null); useEffect(() => { const initDevicesChart = async () => { - if (!deviceData) { - const data = await DataLoader.loadJSON(jsonDataURL); - await setDeviceData(data); - } else { - const chartBuilder = new ChartBuilder(); - await chartBuilder - .setElement(ref.current) - .setData(deviceData) - .setTransformer((d) => { - const devices = transformers.listify(d.totals.by_device); - devices.forEach((device) => { - if (device.key === "smart tv") { - device.key = "Smart TV"; - } - }); - return transformers.findProportionsOfMetricFromValue(devices); - }) - .setRenderer( - barChart() - .value((d) => d.proportion) - .format(formatters.floatToPercent), - ) - .build(); + if (dataFileName) { + let data; + + try { + data = await DataLoader.loadJSON( + `${dataHrefBase}/${dataFileName}.json`, + ); + } catch (e) { + data = { totals: {} }; + } + await buildChartForDeviceData(data); } }; initDevicesChart().catch(console.error); - }, [deviceData]); + }, [dataFileName]); + + function buildChartForDeviceData(data) { + return chartBuilder + .setElement(ref.current) + .setData(data) + .setTransformer((d) => { + const devices = transformers.listify(d.totals.by_device); + devices.forEach((device) => { + if (device.key === "smart tv") { + device.key = "Smart TV"; + } + }); + return transformers.findProportionsOfMetricFromValue(devices); + }) + .setRenderer( + barChart() + .value((d) => d.proportion) + .format(formatters.floatToPercent), + ) + .build(); + } + + async function dataFileChangeHandler(fileName) { + if (!fileName) return; + + await setDataFileName(fileName); + } return ( <> @@ -66,7 +86,10 @@ function DevicesChart({ dataHrefBase }) { Devices - +
+ ); } diff --git a/js/components/dashboard_content/__tests__/DevicesChart.spec.js b/js/components/dashboard_content/__tests__/DevicesChart.spec.js index a0593825..c2786b21 100644 --- a/js/components/dashboard_content/__tests__/DevicesChart.spec.js +++ b/js/components/dashboard_content/__tests__/DevicesChart.spec.js @@ -20,7 +20,7 @@ describe("DevicesChart", () => { return Promise.resolve(null); }); component = render( - , + , ); }); @@ -55,7 +55,7 @@ describe("DevicesChart", () => { return Promise.resolve(data); }); component = render( - , + , ); await waitFor(() => screen.getByText("mobile")); // Wait for barchart transition animation to complete (200 ms, set in @@ -77,16 +77,12 @@ describe("DevicesChart", () => { return Promise.reject(error); }); component = render( - , + , ); }); - it("renders a component in error state", () => { + it("renders a component with an empty chart", () => { expect(component.asFragment()).toMatchSnapshot(); }); - - it("logs the error to console", () => { - expect(console.error).toHaveBeenCalledWith(error); - }); }); }); diff --git a/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap b/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap index 6d1e8a12..9cc53d27 100644 --- a/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap +++ b/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap @@ -37,7 +37,7 @@ exports[`DeviceDemographics renders 1`] = `
+
diff --git a/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap b/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap index 94d79c24..c3e155da 100644 --- a/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap +++ b/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap @@ -30,8 +30,8 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1
+ `; @@ -176,8 +196,8 @@ exports[`DevicesChart when data is not loaded renders a component in loading sta + `; -exports[`DevicesChart when data loading has an error renders a component in error state 1`] = ` +exports[`DevicesChart when data loading has an error renders a component with an empty chart 1`] = `
+ `; diff --git a/js/components/select/FilterSelect.js b/js/components/select/FilterSelect.js new file mode 100644 index 00000000..37217868 --- /dev/null +++ b/js/components/select/FilterSelect.js @@ -0,0 +1,58 @@ +import React, { useEffect, useState } from "react"; +import PropTypes from "prop-types"; + +/** + * Creates a select tag with options populated from the provided props. When an + * option is chosen, the browser is redirected to the page for that agency. + * + * @param {object} props the properties for the component + * @param {string[][]} props.filters the name and value of each filter option. + * @param {string} props.defaultFilterValue the value of the option which should + * be selected by default. + * @param {Function} props.onChange a function to call when the selected option + * is changed. + * @param {string} props.name an accessible name for the select element. + * @returns {import('react').ReactElement} The rendered element + */ +function FilterSelect({ filters, defaultFilterValue, onChange, name }) { + const [selectedFilter, setSelectedFilter] = useState(defaultFilterValue); + const sortedFilters = filters.sort((a, b) => { + // Sort by display name, which is array index 0 + if (a[0] < b[0]) { + return -1; + } else if (a[0] > b[0]) { + return 1; + } + return 0; + }); + + useEffect(() => { + onChange(selectedFilter); + }, [selectedFilter]); + + return ( + + ); +} + +FilterSelect.propTypes = { + filters: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired, + defaultFilterValue: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + name: PropTypes.string.isRequired, +}; + +export default FilterSelect; diff --git a/js/components/agency_select/AgencySelect.js b/js/components/select/agency_select/AgencySelect.js similarity index 100% rename from js/components/agency_select/AgencySelect.js rename to js/components/select/agency_select/AgencySelect.js diff --git a/js/components/agency_select/__tests__/AgencySelect.spec.js b/js/components/select/agency_select/__tests__/AgencySelect.spec.js similarity index 100% rename from js/components/agency_select/__tests__/AgencySelect.spec.js rename to js/components/select/agency_select/__tests__/AgencySelect.spec.js diff --git a/js/components/agency_select/__tests__/__snapshots__/AgencySelect.spec.js.snap b/js/components/select/agency_select/__tests__/__snapshots__/AgencySelect.spec.js.snap similarity index 100% rename from js/components/agency_select/__tests__/__snapshots__/AgencySelect.spec.js.snap rename to js/components/select/agency_select/__tests__/__snapshots__/AgencySelect.spec.js.snap diff --git a/js/components/agency_select/__tests__/index.spec.js b/js/components/select/agency_select/__tests__/index.spec.js similarity index 100% rename from js/components/agency_select/__tests__/index.spec.js rename to js/components/select/agency_select/__tests__/index.spec.js diff --git a/js/components/agency_select/index.js b/js/components/select/agency_select/index.js similarity index 100% rename from js/components/agency_select/index.js rename to js/components/select/agency_select/index.js diff --git a/js/lib/react_setup.js b/js/lib/react_setup.js index 8464a2d1..77e15778 100644 --- a/js/lib/react_setup.js +++ b/js/lib/react_setup.js @@ -1,4 +1,4 @@ -require("../components/agency_select"); +require("../components/select/agency_select"); require("../components/data_downloads"); require("../components/footer"); require("../components/historical_data_download"); From 3dab96b70f0f05023b18464ef801b98c5342c7ad Mon Sep 17 00:00:00 2001 From: Michael Levin Date: Wed, 15 Jan 2025 15:15:36 -0500 Subject: [PATCH 2/2] use gray background with cards --- .../ConsolidatedBarChart.spec.js.snap | 38 +- .../dashboard_content/DashboardContent.js | 2 +- .../dashboard_content/DeviceDemographics.js | 90 ++- .../OperatingSystemsChart.js | 4 +- .../dashboard_content/TopDownloads.js | 2 +- .../__snapshots__/BrowsersChart.spec.js.snap | 20 +- .../DeviceDemographics.spec.js.snap | 442 ++++++------ .../__snapshots__/DevicesChart.spec.js.snap | 16 +- .../OperatingSystemsChart.spec.js.snap | 668 +++++++++--------- .../TopCitiesRealtime.spec.js.snap | 26 +- .../TopCountriesRealtime.spec.js.snap | 140 ++-- .../__snapshots__/TopDownloads.spec.js.snap | 20 +- .../TopDownloadsAndVideoPlays.spec.js.snap | 220 +++--- .../TopLanguagesHistorical.spec.js.snap | 20 +- .../TopPagesHistorical.spec.js.snap | 20 +- .../TopPagesRealtime.spec.js.snap | 20 +- .../__snapshots__/TopVideoPlays.spec.js.snap | 20 +- js/components/select/FilterSelect.js | 2 +- js/lib/chart_helpers/barchart.js | 2 +- sass/_colors.scss | 65 +- sass/elements/_chart.scss | 7 + 21 files changed, 976 insertions(+), 868 deletions(-) diff --git a/js/components/chart/__tests__/__snapshots__/ConsolidatedBarChart.spec.js.snap b/js/components/chart/__tests__/__snapshots__/ConsolidatedBarChart.spec.js.snap index 5474cd1d..046c76f8 100644 --- a/js/components/chart/__tests__/__snapshots__/ConsolidatedBarChart.spec.js.snap +++ b/js/components/chart/__tests__/__snapshots__/ConsolidatedBarChart.spec.js.snap @@ -24,7 +24,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Chrome
@@ -42,7 +42,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Safari
@@ -60,7 +60,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Edge
@@ -78,7 +78,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Firefox
@@ -96,7 +96,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Safari (in-app)
@@ -114,7 +114,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Samsung Internet
@@ -132,7 +132,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Android Webview
@@ -150,7 +150,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Opera
@@ -168,7 +168,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Amazon Silk
@@ -186,7 +186,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Other
@@ -217,7 +217,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Chrome
@@ -235,7 +235,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Safari
@@ -253,7 +253,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Edge
@@ -271,7 +271,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Firefox
@@ -289,7 +289,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Safari (in-app)
@@ -307,7 +307,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Samsung Internet
@@ -325,7 +325,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Android Webview
@@ -343,7 +343,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Opera
@@ -361,7 +361,7 @@ exports[`ConsolidatedBarChart when data is loaded and the expected key exists in class="chart__bar-chart__item margin-bottom-2" >
Amazon Silk
diff --git a/js/components/dashboard_content/DashboardContent.js b/js/components/dashboard_content/DashboardContent.js index 30bef27c..80ed2ec7 100644 --- a/js/components/dashboard_content/DashboardContent.js +++ b/js/components/dashboard_content/DashboardContent.js @@ -298,7 +298,7 @@ function DashboardContent({ dataURL, dataPrefix, agency }) { - +
diff --git a/js/components/dashboard_content/DeviceDemographics.js b/js/components/dashboard_content/DeviceDemographics.js index 72dae58c..b0c8e2c9 100644 --- a/js/components/dashboard_content/DeviceDemographics.js +++ b/js/components/dashboard_content/DeviceDemographics.js @@ -6,6 +6,8 @@ import BrowsersChart from "./BrowsersChart"; import OperatingSystemsChart from "./OperatingSystemsChart"; import ConsolidatedBarChart from "../chart/ConsolidatedBarChart"; import Tooltip from "../tooltip/Tooltip"; +import Card from "../card/Card" +import CardContent from "../card/CardContent" /** * Contains charts and other data visualizations for the user demographics @@ -22,49 +24,65 @@ import Tooltip from "../tooltip/Tooltip"; function DeviceDemographics({ dataHrefBase }) { return (
-
- -
- - - Top Screen Resolutions - - - - - -
-
- -
+
+ + + + + + + + +
+ +
+
+
-
- +
+ + + + +
- + + + + +
); diff --git a/js/components/dashboard_content/OperatingSystemsChart.js b/js/components/dashboard_content/OperatingSystemsChart.js index f8ccd198..0793cb7d 100644 --- a/js/components/dashboard_content/OperatingSystemsChart.js +++ b/js/components/dashboard_content/OperatingSystemsChart.js @@ -59,7 +59,7 @@ function OperatingSystemsChart({ dataHrefBase }) { }, [windowsData, chartsLoaded]); return ( -
+ <> + ); } diff --git a/js/components/dashboard_content/TopDownloads.js b/js/components/dashboard_content/TopDownloads.js index da0259c7..17d8fe68 100644 --- a/js/components/dashboard_content/TopDownloads.js +++ b/js/components/dashboard_content/TopDownloads.js @@ -54,7 +54,7 @@ function TopDownloads({
- + ${formatters.formatURL(d.page)} / diff --git a/js/components/dashboard_content/__tests__/__snapshots__/BrowsersChart.spec.js.snap b/js/components/dashboard_content/__tests__/__snapshots__/BrowsersChart.spec.js.snap index 53d66bc9..2c9cd59f 100644 --- a/js/components/dashboard_content/__tests__/__snapshots__/BrowsersChart.spec.js.snap +++ b/js/components/dashboard_content/__tests__/__snapshots__/BrowsersChart.spec.js.snap @@ -55,7 +55,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Chrome
@@ -73,7 +73,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Safari
@@ -91,7 +91,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Edge
@@ -109,7 +109,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Firefox
@@ -127,7 +127,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Safari (in-app)
@@ -145,7 +145,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Samsung Internet
@@ -163,7 +163,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Android Webview
@@ -181,7 +181,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Opera
@@ -199,7 +199,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Amazon Silk
@@ -217,7 +217,7 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded class="chart__bar-chart__item margin-bottom-2" >
Internet Explorer
diff --git a/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap b/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap index 9cc53d27..71706861 100644 --- a/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap +++ b/js/components/dashboard_content/__tests__/__snapshots__/DeviceDemographics.spec.js.snap @@ -6,254 +6,284 @@ exports[`DeviceDemographics renders 1`] = ` class="padding-top-1 grid-row" > -
+ +
+ +
  • +
  • +
    -
    -
    -
    + +
    +
    -
    +
  • +
    - - +
    +
    +
    +
    +
  • -
    -
    -
    -
    -
    -
    -
    + diff --git a/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap b/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap index c3e155da..7c9a4699 100644 --- a/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap +++ b/js/components/dashboard_content/__tests__/__snapshots__/DevicesChart.spec.js.snap @@ -55,7 +55,7 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1 class="chart__bar-chart__item margin-bottom-2" >
    mobile
    @@ -73,7 +73,7 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1 class="chart__bar-chart__item margin-bottom-2" >
    desktop
    @@ -91,7 +91,7 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1 class="chart__bar-chart__item margin-bottom-2" >
    tablet
    @@ -109,7 +109,7 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1 class="chart__bar-chart__item margin-bottom-2" >
    Smart TV
    @@ -127,7 +127,7 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1 class="chart__bar-chart__item margin-bottom-2" >
    (other)
    @@ -145,7 +145,7 @@ exports[`DevicesChart when data is loaded renders a component with data loaded 1