Skip to content

Commit

Permalink
[Feature] Address feedback for video play feature
Browse files Browse the repository at this point in the history
- Make tests more repeatable by delaying until transition animation is complete
- Display message when video plays are unavailable
- Add JS config class, update realtime refresh seconds to 180
- Rerender download report if number of listings changes
- Fix wording on video/download section descriptions, add factories for tests
  • Loading branch information
levinmr committed Jul 24, 2024
1 parent 988e0ab commit 1b22b14
Show file tree
Hide file tree
Showing 46 changed files with 4,292 additions and 923 deletions.
2 changes: 1 addition & 1 deletion assets/bundle.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions jest_setup.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { faker } from "@faker-js/faker";

global.faker_seed = 123456;

// Set a seed so that faker generated data is consistent for snapshot tests
faker.seed(global.faker_seed);

global.IS_REACT_ACT_ENVIRONMENT = true;

// Define the fetch method because without this we get the error 'fetch is not
Expand Down
13 changes: 7 additions & 6 deletions js/components/dashboard_content/DashboardContent.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from "react";
import PropTypes from "prop-types";

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

/**
* Contains charts and other data visualizations for the main page of the site.
Expand Down Expand Up @@ -38,7 +39,7 @@ function DashboardContent({ dataURL, dataPrefix, agency }) {
<RealtimeVisitors
dataHrefBase={dataHrefBase}
agency={agency}
refreshSeconds={15}
refreshSeconds={Config.realtimeDataRefreshSeconds}
/>
</article>

Expand Down
8 changes: 6 additions & 2 deletions js/components/dashboard_content/LocationsAndLanguages.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";

import Config from "../../lib/config";
import TopCitiesRealtime from "./TopCitiesRealtime";
import TopCountriesRealtime from "./TopCountriesRealtime";
import TopLanguagesHistorical from "./TopLanguagesHistorical";
Expand Down Expand Up @@ -30,7 +31,10 @@ function LocationsAndLanguages({ dataHrefBase }) {
className="desktop:grid-col-4 padding-2 bar-chart-component"
>
<h4>Cities</h4>
<TopCitiesRealtime dataHrefBase={dataHrefBase} refreshSeconds={15} />
<TopCitiesRealtime
dataHrefBase={dataHrefBase}
refreshSeconds={Config.realtimeDataRefreshSeconds}
/>
</section>

<section
Expand All @@ -40,7 +44,7 @@ function LocationsAndLanguages({ dataHrefBase }) {
<h4>Countries</h4>
<TopCountriesRealtime
dataHrefBase={dataHrefBase}
refreshSeconds={15}
refreshSeconds={Config.realtimeDataRefreshSeconds}
/>
</section>

Expand Down
127 changes: 28 additions & 99 deletions js/components/dashboard_content/SidebarContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React from "react";
import PropTypes from "prop-types";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";

import TopDownloads from "./TopDownloads";
import Config from "../../lib/config";
import TopDownloadsAndVideoPlays from "./TopDownloadsAndVideoPlays";
import TopPagesRealtime from "./TopPagesRealtime";
import TopPagesHistorical from "./TopPagesHistorical";
import TopVideoPlays from "./TopVideoPlays";

/**
* Contains charts and other data visualizations for the top pages, top
Expand All @@ -15,6 +15,7 @@ import TopVideoPlays from "./TopVideoPlays";
* components. The top pages chart, top downloads chart, and top video plays
* displayed correspond to the time interval selected in the tabs.
*
*
* Note that the 30 minutes time interval is how often Google Analytics 4
* updates realtime reports. The dimensions/metrics needed to report on file
* downloads are not available in the realtime reporting API, so for the 30
Expand All @@ -30,8 +31,6 @@ import TopVideoPlays from "./TopVideoPlays";
*/
function SidebarContent({ dataHrefBase, agency }) {
const numberOfTopPagesToDisplay = 30;
const numberOfTopDownloadsToDisplay = 10;
const numberOfTopVideoPlaysToDisplay = 10;

return (
<section className="sidebar-content">
Expand Down Expand Up @@ -69,37 +68,17 @@ function SidebarContent({ dataHrefBase, agency }) {
dataHrefBase={dataHrefBase}
reportFileName="top-pages-realtime.json"
numberOfListingsToDisplay={numberOfTopPagesToDisplay}
refreshSeconds={15}
/>
</section>
<section className="top-downloads">
<div className="top-downloads__headline">
<h3>Top {numberOfTopDownloadsToDisplay} Downloads Yesterday</h3>
</div>
<h4>
<em>Total file downloads yesterday on {agency} hostnames.</em>
</h4>
<TopDownloads
dataHrefBase={dataHrefBase}
reportFileName="top-downloads-yesterday.json"
numberOfListingsToDisplay={numberOfTopDownloadsToDisplay}
/>
</section>
<section className="top-video-plays">
<div className="top-video-plays__headline">
<h3>
Top {numberOfTopVideoPlaysToDisplay} Video Plays Yesterday
</h3>
</div>
<h4>
<em>Total videos played yesterday on {agency} hostnames.</em>
</h4>
<TopVideoPlays
dataHrefBase={dataHrefBase}
reportFileName="top-video-plays-yesterday.json"
numberOfListingsToDisplay={numberOfTopDownloadsToDisplay}
refreshSeconds={Config.realtimeDataRefreshSeconds}
/>
</section>
<TopDownloadsAndVideoPlays
dataHrefBase={dataHrefBase}
agency={agency}
downloadsReportFileName="top-downloads-yesterday.json"
videoPlaysReportFileName="top-video-plays-yesterday.json"
timeIntervalHeader="Yesterday"
timeIntervalDescription="yesterday"
/>
</TabPanel>
<TabPanel>
<section className="top-pages">
Expand All @@ -114,38 +93,14 @@ function SidebarContent({ dataHrefBase, agency }) {
numberOfListingsToDisplay={numberOfTopPagesToDisplay}
/>
</section>
<section className="top-downloads">
<div className="top-downloads__headline">
<h3>Top {numberOfTopDownloadsToDisplay} Downloads Last 7 Days</h3>
</div>
<h4>
<em>
Total file downloads over the last week on {agency} hostnames.
</em>
</h4>
<TopDownloads
dataHrefBase={dataHrefBase}
reportFileName="top-downloads-7-days.json"
numberOfListingsToDisplay={numberOfTopDownloadsToDisplay}
/>
</section>
<section className="top-video-plays">
<div className="top-video-plays__headline">
<h3>
Top {numberOfTopVideoPlaysToDisplay} Video Plays Last 7 Days
</h3>
</div>
<h4>
<em>
Total videos played over the last week on {agency} hostnames.
</em>
</h4>
<TopVideoPlays
dataHrefBase={dataHrefBase}
reportFileName="top-video-plays-7-days.json"
numberOfListingsToDisplay={numberOfTopDownloadsToDisplay}
/>
</section>
<TopDownloadsAndVideoPlays
dataHrefBase={dataHrefBase}
agency={agency}
downloadsReportFileName="top-downloads-7-days.json"
videoPlaysReportFileName="top-video-plays-7-days.json"
timeIntervalHeader="Last 7 Days"
timeIntervalDescription="over the last week"
/>
</TabPanel>
<TabPanel>
<section className="top-pages">
Expand All @@ -164,40 +119,14 @@ function SidebarContent({ dataHrefBase, agency }) {
numberOfListingsToDisplay={numberOfTopPagesToDisplay}
/>
</section>
<section className="top-downloads">
<div className="top-downloads__headline">
<h3>
Top {numberOfTopDownloadsToDisplay} Downloads Last 30 Days
</h3>
</div>
<h4>
<em>
Total file downloads over the last month on {agency} hostnames.
</em>
</h4>
<TopDownloads
dataHrefBase={dataHrefBase}
reportFileName="top-downloads-30-days.json"
numberOfListingsToDisplay={numberOfTopDownloadsToDisplay}
/>
</section>
<section className="top-video-plays">
<div className="top-video-plays__headline">
<h3>
Top {numberOfTopVideoPlaysToDisplay} Video Plays Last 30 Days
</h3>
</div>
<h4>
<em>
Total videos played over the last month on {agency} hostnames.
</em>
</h4>
<TopVideoPlays
dataHrefBase={dataHrefBase}
reportFileName="top-video-plays-30-days.json"
numberOfListingsToDisplay={numberOfTopDownloadsToDisplay}
/>
</section>
<TopDownloadsAndVideoPlays
dataHrefBase={dataHrefBase}
agency={agency}
downloadsReportFileName="top-downloads-30-days.json"
videoPlaysReportFileName="top-video-plays-30-days.json"
timeIntervalHeader="Last 30 Days"
timeIntervalDescription="over the last month"
/>
</TabPanel>
</Tabs>
</section>
Expand Down
2 changes: 1 addition & 1 deletion js/components/dashboard_content/TopDownloads.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function TopDownloads({
}
};
initDownloadsChart().catch(console.error);
}, [downloadData]);
}, [downloadData, numberOfListingsToDisplay]);

return (
<figure className="top-downloads__bar-chart" ref={ref}>
Expand Down
122 changes: 122 additions & 0 deletions js/components/dashboard_content/TopDownloadsAndVideoPlays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";

import DataLoader from "../../lib/data_loader";
import TopDownloads from "./TopDownloads";
import TopVideoPlays from "./TopVideoPlays";

/**
* Contains charts and other data visualizations for the top downloads and top
* video plays section of the site.
*
* @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.
* @param {string} props.agency the display name for the current agency.
* @param {string} props.downloadsReportFileName the file name of the report to
* use as a data source for the download data visualization.
* @param {string} props.videoPlaysReportFileName the file name of the report to
* use as a data source for the video play data visualization.
* @param {string} props.timeIntervalHeader the report time interval to
* display in the header for each section.
* @param {string} props.timeIntervalDescription the report time interval to
* display in the subheader for each section.
* @returns {import('react').ReactElement} The rendered element
*/
function TopDownloadsAndVideoPlays({
dataHrefBase,
agency,
downloadsReportFileName,
videoPlaysReportFileName,
timeIntervalHeader,
timeIntervalDescription,
}) {
const numberOfTopDownloadsToDisplay = 10;
const numberOfTopVideoPlaysToDisplay = 10;

const videoDataURL = `${dataHrefBase}/${videoPlaysReportFileName}`;
const [videoPlayData, setVideoPlayData] = useState(null);

useEffect(() => {
const getVideoPlayData = async () => {
if (!videoPlayData) {
const data = await DataLoader.loadJSON(videoDataURL);
await setVideoPlayData(data);
}
};
getVideoPlayData().catch(console.error);
}, []);

function __shouldDisplayVideoPlays() {
return (
videoPlayData &&
videoPlayData.data &&
Array.isArray(videoPlayData.data) &&
videoPlayData.data.length > 2
);
}

function __topDownloadsCount() {
return __shouldDisplayVideoPlays(videoPlayData)
? numberOfTopDownloadsToDisplay
: numberOfTopDownloadsToDisplay + numberOfTopVideoPlaysToDisplay;
}

return (
<>
<section className="top-downloads">
<div className="top-downloads__headline">
<h3>
Top {__topDownloadsCount()} Downloads {timeIntervalHeader}
</h3>
</div>
<h4>
<em>
Top file downloads {timeIntervalDescription} on {agency} hostnames.
</em>
</h4>
<TopDownloads
dataHrefBase={dataHrefBase}
reportFileName={downloadsReportFileName}
numberOfListingsToDisplay={__topDownloadsCount()}
/>
</section>
<section className="top-video-plays">
<div className="top-video-plays__headline">
<h3>
{__shouldDisplayVideoPlays()
? `Top ${numberOfTopVideoPlaysToDisplay} Video Plays ${timeIntervalHeader}`
: `Top Video Plays ${timeIntervalHeader}`}
</h3>
</div>
<h4>
<em>
{__shouldDisplayVideoPlays()
? `Top videos played ${timeIntervalDescription} on ${agency} hostnames.`
: `Video play data is unavailable for ${agency} hostnames.`}
</em>
</h4>
{__shouldDisplayVideoPlays() ? (
<TopVideoPlays
videoPlayData={videoPlayData}
numberOfListingsToDisplay={numberOfTopVideoPlaysToDisplay}
/>
) : (
<div></div>
)}
</section>
</>
);
}

TopDownloadsAndVideoPlays.propTypes = {
dataHrefBase: PropTypes.string.isRequired,
agency: PropTypes.string.isRequired,
downloadsReportFileName: PropTypes.string.isRequired,
videoPlaysReportFileName: PropTypes.string.isRequired,
timeIntervalHeader: PropTypes.string.isRequired,
timeIntervalDescription: PropTypes.string.isRequired,
};

export default TopDownloadsAndVideoPlays;
Loading

0 comments on commit 1b22b14

Please sign in to comment.