Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- lint-js
- lint-styles
- test
if: github.ref == 'refs/heads/develop'
if: github.ref == 'refs/heads/feature/pie_chart_poc'
uses: 18F/analytics.usa.gov/.github/workflows/deploy.yml@develop
with:
API_APP_NAME: ${{ vars.API_APP_NAME_DEV }}
Expand Down
4 changes: 3 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module.exports = [
"sass/**/*",
"ga4-data/**/*",
"js/lib/touchpoints.js",
"js/lib/chart_helpers/pie_chart.js",
],
},
{
Expand Down Expand Up @@ -92,10 +93,11 @@ module.exports = [
"ga4-data/**/*",
"js/lib/touchpoints.js",
"**/__tests__/*.js",
"js/lib/chart_helpers/pie_chart.js",
],
rules: {
...jsdoc.configs.recommended.rules,
"jsdoc/check-indentation": "error",
"jsdoc/check-indentation": "warn",
"jsdoc/check-line-alignment": "error",
"jsdoc/check-syntax": "error",
"jsdoc/convert-to-jsdoc-comments": "error",
Expand Down
6 changes: 6 additions & 0 deletions jest_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ faker.seed(global.faker_seed);

global.IS_REACT_ACT_ENVIRONMENT = true;

global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));

// Define the fetch method because without this we get the error 'fetch is not
// defined' in the tests. TODO: figure out why node's native fetch doesn't
// show as defined in the tests.
Expand Down
20 changes: 20 additions & 0 deletions js/components/dashboard_content/DashboardContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import PropTypes from "prop-types";

import Config from "../../lib/config";
import DeviceDemographics from "./DeviceDemographics";
import DevicesPieChart from "./DevicesPieChart";
import Engagement from "./Engagement";
import LocationsAndLanguages from "./LocationsAndLanguages";
import RealtimeVisitors from "./RealtimeVisitors";
import Sessions30Days from "./Sessions30Days";
import SidebarContent from "./SidebarContent";
import TrafficSources from "./TrafficSources";
import Visitors30Days from "./Visitors30Days";
import OperatingSystemsPieChart from "./OperatingSystemsPieChart";

/**
* Contains charts and other data visualizations for the main page of the site.
Expand Down Expand Up @@ -47,6 +49,24 @@ function DashboardContent({ dataURL, dataPrefix, agency }) {
<LocationsAndLanguages dataHrefBase={dataHrefBase} />
</article>

<article className="section">
<div className="section__chart grid-row">
<section className="desktop:grid-col-6 padding-2">
<div className="section__headline">
<h3>Device</h3>
</div>
<DevicesPieChart dataHrefBase={dataHrefBase} />
</section>

<section className="desktop:grid-col-6 padding-2">
<div className="section__headline">
<h3>Operating System</h3>
</div>
<OperatingSystemsPieChart dataHrefBase={dataHrefBase} />
</section>
</div>
</article>

<article className="section">
<div className="section__headline">
<h2>Historical Data and Trends</h2>
Expand Down
69 changes: 69 additions & 0 deletions js/components/dashboard_content/DevicesPieChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useRef, useEffect, useState } from "react";
import PropTypes from "prop-types";
import colorbrewer from "colorbrewer";

import DataLoader from "../../lib/data_loader";
import renderPieChart from "../../lib/chart_helpers/pie_chart";
import transformers from "../../lib/chart_helpers/transformers";

/**
* Retrieves the devices report from the passed data URL and creates a
* visualization for the breakdown of devices of users visiting sites for the
* current agency.
*
* @param {object} props the properties for the component
* @param {string} props.dataHrefBase the URL of the base location of the data
* to be downloaded including the agency path. In production this is proxied and
* redirected to the S3 bucket URL.
* @returns {import('react').ReactElement} The rendered element
*/
function DevicesPieChart({ dataHrefBase }) {
const dataURL = `${dataHrefBase}/devices.json`;
const ref = useRef(null);
const [deviceData, setDeviceData] = useState(null);
const [pieSvgWidth, setPieSvgWidth] = useState(null);

useEffect(() => {
const initDevicesChart = async () => {
if (!pieSvgWidth) {
const resizeObserver = new ResizeObserver((entries) => {
console.log(entries[0].target.getBoundingClientRect().width);
const element = entries[0].target;
setPieSvgWidth(element.getBoundingClientRect().width);
});
resizeObserver.observe(ref.current);
}
if (!deviceData) {
const data = await DataLoader.loadJSON(dataURL);
await setDeviceData(data);
} else {
const devices = transformers.listify(deviceData.totals.by_device);
devices.forEach((device) => {
device.key = device.key === "smart tv" ? "Smart TV" : device.key;
});
const dataWithProportions =
transformers.findProportionsOfMetricFromValue(devices);

await renderPieChart({
ref: ref.current,
data: dataWithProportions,
width: pieSvgWidth,
colorSet: colorbrewer[colorbrewer.schemeGroups.qualitative[5]][8],
});
}
};
initDevicesChart().catch(console.error);
}, [deviceData, pieSvgWidth]);

return (
<>
<figure id="pie_chart_device_types" ref={ref}></figure>
</>
);
}

DevicesPieChart.propTypes = {
dataHrefBase: PropTypes.string.isRequired,
};

export default DevicesPieChart;
66 changes: 66 additions & 0 deletions js/components/dashboard_content/OperatingSystemsPieChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useRef, useEffect, useState } from "react";
import PropTypes from "prop-types";
import colorbrewer from "colorbrewer";

import DataLoader from "../../lib/data_loader";
import renderPieChart from "../../lib/chart_helpers/pie_chart";
import transformers from "../../lib/chart_helpers/transformers";

/**
* Retrieves the OperatingSystems report from the passed data URL and creates a
* visualization for the breakdown of OperatingSystems of users visiting sites for the
* current agency.
*
* @param {object} props the properties for the component
* @param {string} props.dataHrefBase the URL of the base location of the data
* to be downloaded including the agency path. In production this is proxied and
* redirected to the S3 bucket URL.
* @returns {import('react').ReactElement} The rendered element
*/
function OperatingSystemsPieChart({ dataHrefBase }) {
const dataURL = `${dataHrefBase}/os.json`;
const ref = useRef(null);
const [osData, setOsData] = useState(null);
const [pieSvgWidth, setPieSvgWidth] = useState(null);

useEffect(() => {
const initOsChart = async () => {
if (!pieSvgWidth) {
const resizeObserver = new ResizeObserver((entries) => {
console.log(entries[0].target.getBoundingClientRect().width);
const element = entries[0].target;
setPieSvgWidth(element.getBoundingClientRect().width);
});
resizeObserver.observe(ref.current);
}
if (!osData) {
const data = await DataLoader.loadJSON(dataURL);
await setOsData(data);
} else {
const operatingSystems = transformers.listify(osData.totals.by_os);
const dataWithProportions =
transformers.findProportionsOfMetricFromValue(operatingSystems);

await renderPieChart({
ref: ref.current,
data: dataWithProportions,
width: pieSvgWidth,
colorSet: colorbrewer[colorbrewer.schemeGroups.qualitative[2]][8],
});
}
};
initOsChart().catch(console.error);
}, [osData, pieSvgWidth]);

return (
<>
<figure id="pie_chart_device_types" ref={ref}></figure>
</>
);
}

OperatingSystemsPieChart.propTypes = {
dataHrefBase: PropTypes.string.isRequired,
};

export default OperatingSystemsPieChart;
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
48.7%
49%
</div>
<div
class="bar"
style="width: 49%;"
style="width: 48.7%;"
/>
</div>
<div
Expand All @@ -37,11 +37,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
35.8%
36%
</div>
<div
class="bar"
style="width: 36%;"
style="width: 35.8%;"
/>
</div>
<div
Expand All @@ -55,11 +55,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
8.8%
9%
</div>
<div
class="bar"
style="width: 9%;"
style="width: 8.8%;"
/>
</div>
<div
Expand All @@ -73,11 +73,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
1.9%
2%
</div>
<div
class="bar"
style="width: 2%;"
style="width: 1.9%;"
/>
</div>
<div
Expand All @@ -91,11 +91,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
1.9%
2%
</div>
<div
class="bar"
style="width: 2%;"
style="width: 1.9%;"
/>
</div>
<div
Expand All @@ -109,11 +109,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
1.4%
1%
</div>
<div
class="bar"
style="width: 1%;"
style="width: 1.4%;"
/>
</div>
<div
Expand Down Expand Up @@ -145,11 +145,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
0.2%
0%
</div>
<div
class="bar"
style="width: 0%;"
style="width: 0.2%;"
/>
</div>
<div
Expand All @@ -163,11 +163,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
0.1%
0%
</div>
<div
class="bar"
style="width: 0%;"
style="width: 0.1%;"
/>
</div>
<div
Expand All @@ -181,11 +181,11 @@ exports[`BrowsersChart when data is loaded renders a component with data loaded
<div
class="value"
>
0.1%
0%
</div>
<div
class="bar"
style="width: 0%;"
style="width: 0.1%;"
/>
</div>
</div>
Expand Down
Loading