Skip to content

Commit

Permalink
Merge pull request #9332 from ForumMagnum/master
Browse files Browse the repository at this point in the history
[EA] deploy analytics page improvements, LW stuff
  • Loading branch information
oetherington committed May 24, 2024
2 parents 3818983 + b49fc86 commit aeb78ba
Show file tree
Hide file tree
Showing 31 changed files with 1,434 additions and 342 deletions.
33 changes: 23 additions & 10 deletions packages/lesswrong/components/analytics/AnalyticsDisclaimers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from "react";
import { Components, registerComponent } from "../../lib/vulcan-lib";
import moment from "moment";
import { forumSelect } from "../../lib/forumTypeUtils";
import { isFriendlyUI } from "@/themes/forumTheme";
import { GRAPH_LEFT_MARGIN } from "./AnalyticsGraph";

const missingClientRangeText = forumSelect({
EAForum: "Jan 11th - Jun 14th of 2021",
Expand All @@ -14,38 +16,49 @@ const missingClientLastDay = forumSelect({
default: null,
});
const dataCollectionFirstDay = forumSelect({
EAForum: "Feb 19th, 2020",
EAForum: "on Feb 19th, 2020",
LWAF: "around the start of 2020",
default: null,
});

const styles = (theme: ThemeType): JssStyles => ({
root: {}
const styles = (theme: ThemeType) => ({
root: isFriendlyUI
? {
fontFamily: theme.palette.fonts.sansSerifStack,
margin: `0 ${GRAPH_LEFT_MARGIN}px`,
}
: {},
});

const AnalyticsDisclaimers = ({ earliestDate }: { earliestDate: Date }) => {
const { Typography } = Components;

const AnalyticsDisclaimers = ({earliestDate, classes}: {
earliestDate: Date,
classes: ClassesType<typeof styles>,
}) => {
const {Typography} = Components;
return (
<>
{missingClientLastDay && moment(earliestDate) < moment(missingClientLastDay) && (
<Typography variant="body1" gutterBottom>
<Typography variant="body1" gutterBottom className={classes.root}>
<em>
Note: For figures that rely on detecting unique devices, we were mistakenly not collecting that data from{" "}
{missingClientRangeText}.
</em>
</Typography>
)}
{dataCollectionFirstDay && moment(earliestDate) < moment("2020-02-19") && (
<Typography variant="body1" gutterBottom>
<em>Note 2: Data collection began on {dataCollectionFirstDay}.</em>
<Typography variant="body1" gutterBottom className={classes.root}>
<em>Note 2: Data collection began {dataCollectionFirstDay}.</em>
</Typography>
)}
</>
);
};

const AnalyticsDisclaimersComponent = registerComponent("AnalyticsDisclaimers", AnalyticsDisclaimers, { styles });
const AnalyticsDisclaimersComponent = registerComponent(
"AnalyticsDisclaimers",
AnalyticsDisclaimers,
{styles},
);

declare global {
interface ComponentTypes {
Expand Down
197 changes: 132 additions & 65 deletions packages/lesswrong/components/analytics/AnalyticsGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ import { AnalyticsField, analyticsFieldsList, useAnalyticsSeries } from "../hook
import startCase from "lodash/startCase";
import Checkbox, { CheckboxProps } from "@material-ui/core/Checkbox";
import { useDialog } from "../common/withDialog";
import classNames from "classnames";

const GRAPH_HEIGHT = 300;
const CONTROLS_BREAKPOINT = 650;

const styles = (theme: ThemeType): JssStyles => ({
export const GRAPH_HEIGHT = 300;

/**
* The garph is rendered as an SVG which includes a built-in left margin - this
* constant is the offset we need to apply to other elements to make them
* line up nicely with the graph.
*/
export const GRAPH_LEFT_MARGIN = 14;

export const styles = (theme: ThemeType) => ({
root: {
overflow: "auto hidden",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: "12px",
fontFamily: theme.palette.fonts.sansSerifStack,
},
graphContainer: {
Expand All @@ -22,39 +35,6 @@ const styles = (theme: ThemeType): JssStyles => ({
fontWeight: 500,
},
},
graphHeader: {
display: "flex",
justifyContent: "space-between",
flexDirection: "row",
marginBottom: 12,
[theme.breakpoints.down('xs')]: {
flexDirection: "column",
minHeight: 56,
marginBottom: 0,
}
},
graphHeaderSmallerTitle: {
[theme.breakpoints.down('xs')]: {
minHeight: 42,
}
},
graphHeaderNoTitle: {
[theme.breakpoints.down('xs')]: {
minHeight: 0,
}
},
graphHeading: {
fontSize: 32,
fontWeight: "600",
color: theme.palette.grey[1000],
fontFamily: theme.palette.fonts.sansSerifStack,
[theme.breakpoints.down('xs')]: {
lineHeight: '1.2em',
}
},
smallerTitle: {
fontSize: 20,
},
fetchingLatest: {
fontSize: 14,
color: theme.palette.grey[600],
Expand All @@ -68,6 +48,7 @@ const styles = (theme: ThemeType): JssStyles => ({
},
tooltip: {
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.dropdown.border}`,
boxShadow: theme.palette.boxShadow.graphTooltip,
padding: 10,
borderRadius: theme.borderRadius.small,
Expand All @@ -84,14 +65,19 @@ const styles = (theme: ThemeType): JssStyles => ({
},
notEnoughDataMessage: {
color: theme.palette.grey[500],
fontFamily: theme.palette.fonts.sansSerifStack,
},
controls: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
flexDirection: "row",
[theme.breakpoints.down('xs')]: {
alignItems: "flex-end",
gap: "32px",
width: "100%",
paddingLeft: 18,
paddingRight: 28,
[`@media(max-width: ${CONTROLS_BREAKPOINT}px)`]: {
flexDirection: "column",
alignItems: "flex-start",
gap: "20px",
}
},
controlFields: {
Expand All @@ -100,16 +86,21 @@ const styles = (theme: ThemeType): JssStyles => ({
color: theme.palette.grey[900],
fontSize: 14,
fontWeight: 500,
[theme.breakpoints.down('xs')]: {
width: '100%',
display: "grid",
// Flow into grid with 2 per row on large screens, 1 per row on small screens. Ideally never 3 per row.
gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))",
marginBottom: -6,
[`@media(max-width: ${CONTROLS_BREAKPOINT}px)`]: {
flexWrap: "wrap",
marginLeft: -14,
},
},
fieldLabel: {
display: "flex",
alignItems: "center",
["@media(max-width: 440px)"]: {
flexBasis: "50%",
},
["@media(max-width: 290px)"]: {
flexBasis: "100%",
},
},
checkbox: {
padding: '8px 6px 8px 16px',
Expand All @@ -131,9 +122,41 @@ const styles = (theme: ThemeType): JssStyles => ({
opacity: 0.8,
},
dateDropdown: {
alignSelf: "flex-start",
margin: '4px 20px 0 0',
}
marginLeft: 4,
marginBottom: 16,
color: theme.palette.grey[1000],
"& .MuiButton-label": {
fontSize: 18,
fontWeight: 600,
},
},
overallStat: {
display: "flex",
flexDirection: "column",
alignItems: "center",
fontSize: 13,
fontWeight: 500,
},
overallStatContainer: {
display: "flex",
gap: "32px",
flexGrow: 1,
},
overallStatCount: {
fontSize: 32,
fontWeight: 700,
},
overallStatTooltip: {
maxWidth: 150,
color: theme.palette.grey[0],
background: theme.palette.grey[1000],
fontFamily: theme.palette.fonts.sansSerifStack,
borderRadius: theme.borderRadius.default,
padding: "6px 10px",
textAlign: "center",
fontSize: 12,
fontWeight: 500,
},
});

const LINE_COLORS: Record<AnalyticsField, string> = {
Expand Down Expand Up @@ -199,7 +222,7 @@ const startEndDateFromOption = (option: string) => {

interface ColoredCheckboxProps extends CheckboxProps {
fillColor: string;
classes: ClassesType;
classes: ClassesType<typeof styles>;
}

const ColoredCheckbox: React.FC<ColoredCheckboxProps> = ({ fillColor, classes, ...props }: ColoredCheckboxProps) => {
Expand All @@ -221,18 +244,16 @@ export const AnalyticsGraph = ({
userId,
postIds,
initialDisplayFields = ["views", "reads"],
title,
smallerTitle = false,
disclaimerEarliestDate,
classes,
}: {
initialDisplayFields?: AnalyticsField[];
userId?: string;
postIds?: string[];
title?: string;
smallerTitle?: boolean;
classes: ClassesType;
disclaimerEarliestDate?: Date,
classes: ClassesType<typeof styles>;
}) => {
const { Typography, ForumDropdown } = Components;
const {Typography, ForumDropdown, LWTooltip} = Components;

const [displayFields, setDisplayFields] = useState<AnalyticsField[]>(initialDisplayFields);
const [dateOption, setDateOption] = useState<string>(dateOptions.last30Days.value);
Expand All @@ -243,7 +264,7 @@ export const AnalyticsGraph = ({

const { openDialog } = useDialog();

const { analyticsSeries: dataSeries } = useAnalyticsSeries({
const {analyticsSeries: dataSeries, loading} = useAnalyticsSeries({
userId,
postIds,
startDate: displayStartDate,
Expand Down Expand Up @@ -292,7 +313,13 @@ export const AnalyticsGraph = ({
};
});

const getTooltipContent = useCallback(({ active, payload, label }: TooltipProps<string, string>) => {
const overallStats = dataSeries.reduce((totals, dataPoint) => {
totals.views += dataPoint.views ?? 0;
totals.reads += dataPoint.reads ?? 0;
return totals;
}, {views: 0, reads: 0});

const getTooltipContent = useCallback(({ active, payload }: TooltipProps<string, string>) => {
if (!(active && payload && payload.length)) return null;

const date = new Date(payload[0].payload["date"]);
Expand All @@ -309,6 +336,22 @@ export const AnalyticsGraph = ({
);
}, [classes.date, classes.tooltip, classes.tooltipLabel, displayFields]);

const dateOptionDropdown = (
<ForumDropdown
value={dateOption}
options={dateOptions}
onSelect={handleDateOptionChange}
className={classes.dateDropdown}
/>
);

const {AnalyticsGraphSkeleton, AnalyticsDisclaimers} = Components;
if (loading || (!userId && !postIds?.length)) {
return (
<AnalyticsGraphSkeleton dateOptionDropdown={dateOptionDropdown} />
);
}

if (!dataSeriesToDisplay?.length || dataSeriesToDisplay.length === 1) {
return (
<Typography variant="body1" className={classes.notEnoughDataMessage}>
Expand All @@ -321,19 +364,37 @@ export const AnalyticsGraph = ({
const currentMaxValue = Math.max(...displayFields.map(field => dataPoint[field as AnalyticsField] ?? 0));
return Math.max(maxVal, currentMaxValue);
}, 0);

// Unfortunately this is the best workaround, see here: https://github.com/recharts/recharts/issues/2027
const yAxisWidth = 26 + Math.ceil(maxValue.toLocaleString().length * 6);
const strokeWidth = dataSeriesToDisplay.length > 180 ? 2 : 2;

return (
<div className={classes.root}>
<div className={classNames(classes.graphHeader, {[classes.graphHeaderSmallerTitle]: smallerTitle, [classes.graphHeaderNoTitle]: !title})}>
<Typography variant="headline" className={classNames(classes.graphHeading, {[classes.smallerTitle]: smallerTitle})}>
{title}
</Typography>
</div>
{dateOptionDropdown}
<div className={classes.controls}>
<div className={classes.overallStatContainer}>
<LWTooltip
title="When someone clicks on your post it’s counted as a view"
placement="bottom"
tooltip={false}
popperClassName={classes.overallStatTooltip}
className={classes.overallStat}
>
<div className={classes.overallStatCount}>{overallStats.views}</div>
<div>Views</div>
</LWTooltip>
<LWTooltip
title="When someone views your post for longer than 30 sec it’s counted as a read"
placement="bottom"
tooltip={false}
popperClassName={classes.overallStatTooltip}
className={classes.overallStat}
>
<div className={classes.overallStatCount}>{overallStats.reads}</div>
<div>Reads</div>
</LWTooltip>
</div>
<div className={classes.controlFields}>
{analyticsFieldsList.map((field) => (
<label key={field} className={classes.fieldLabel}>
Expand All @@ -347,7 +408,6 @@ export const AnalyticsGraph = ({
</label>
))}
</div>
<ForumDropdown value={dateOption} options={dateOptions} className={classes.dateDropdown} onSelect={handleDateOptionChange} />
</div>
<ResponsiveContainer width="100%" height={GRAPH_HEIGHT} className={classes.graphContainer}>
<LineChart data={dataSeriesToDisplay} height={300} margin={{ right: 30 }}>
Expand All @@ -364,11 +424,18 @@ export const AnalyticsGraph = ({
))}
</LineChart>
</ResponsiveContainer>
<AnalyticsDisclaimers
earliestDate={disclaimerEarliestDate ?? displayStartDate ?? new Date(0)}
/>
</div>
);
};

const AnalyticsGraphComponent = registerComponent("AnalyticsGraph", AnalyticsGraph, { styles });
const AnalyticsGraphComponent = registerComponent(
"AnalyticsGraph",
AnalyticsGraph,
{styles},
);

declare global {
interface ComponentTypes {
Expand Down
Loading

0 comments on commit aeb78ba

Please sign in to comment.