build out retention reports

This commit is contained in:
Francis Cao 2023-08-04 13:10:03 -07:00
parent 253a46460b
commit 9cde107ddf
14 changed files with 479 additions and 3 deletions

View file

@ -161,6 +161,7 @@ export const labels = defineMessages({
overview: { id: 'labels.overview', defaultMessage: 'Overview' },
totalRecords: { id: 'labels.total-records', defaultMessage: 'Total records' },
insights: { id: 'label.insights', defaultMessage: 'Insights' },
retention: { id: 'label.retention', defaultMessage: 'Retention' },
dropoff: { id: 'label.dropoff', defaultMessage: 'Dropoff' },
});

View file

@ -1,9 +1,11 @@
import FunnelReport from './funnel/FunnelReport';
import EventDataReport from './event-data/EventDataReport';
import RetentionReport from './retention/RetentionReport';
const reports = {
funnel: FunnelReport,
'event-data': EventDataReport,
retention: RetentionReport,
};
export default function ReportDetails({ reportId, reportType }) {

View file

@ -47,6 +47,12 @@ export function ReportTemplates() {
url: '/reports/funnel',
icon: <Funnel />,
},
{
title: formatMessage(labels.retention),
description: 'Track your websites user retention',
url: '/reports/retention',
icon: <Funnel />,
},
];
return (

View file

@ -1,5 +1,5 @@
import { useCallback, useContext, useMemo } from 'react';
import { Loading } from 'react-basics';
import { Loading, StatusLight } from 'react-basics';
import useMessages from 'hooks/useMessages';
import useTheme from 'hooks/useTheme';
import BarChart from 'components/metrics/BarChart';
@ -22,14 +22,25 @@ export function FunnelChart({ className, loading }) {
);
const renderTooltipPopup = useCallback((setTooltipPopup, model) => {
const { opacity, dataPoints } = model.tooltip;
const { opacity, labelColors, dataPoints } = model.tooltip;
if (!dataPoints?.length || !opacity) {
setTooltipPopup(null);
return;
}
setTooltipPopup(`${formatLongNumber(dataPoints[0].raw.y)} ${formatMessage(labels.visitors)}`);
setTooltipPopup(
<>
<div>
{formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)}
</div>
<div>
<StatusLight color={labelColors?.[0]?.backgroundColor}>
{formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)}
</StatusLight>
</div>
</>,
);
}, []);
const datasets = useMemo(() => {

View file

@ -0,0 +1,74 @@
import { useCallback, useContext, useMemo } from 'react';
import { Loading, StatusLight } from 'react-basics';
import useMessages from 'hooks/useMessages';
import useTheme from 'hooks/useTheme';
import BarChart from 'components/metrics/BarChart';
import { formatLongNumber } from 'lib/format';
import styles from './RetentionChart.module.css';
import { ReportContext } from '../Report';
export function RetentionChart({ className, loading }) {
const { report } = useContext(ReportContext);
const { formatMessage, labels } = useMessages();
const { colors } = useTheme();
const { parameters, data } = report || {};
const renderXLabel = useCallback(
(label, index) => {
return parameters.urls[index];
},
[parameters],
);
const renderTooltipPopup = useCallback((setTooltipPopup, model) => {
const { opacity, labelColors, dataPoints } = model.tooltip;
if (!dataPoints?.length || !opacity) {
setTooltipPopup(null);
return;
}
setTooltipPopup(
<>
<div>
{formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)}
</div>
<div>
<StatusLight color={labelColors?.[0]?.backgroundColor}>
{formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)}
</StatusLight>
</div>
</>,
);
}, []);
const datasets = useMemo(() => {
return [
{
label: formatMessage(labels.uniqueVisitors),
data: data,
borderWidth: 1,
...colors.chart.visitors,
},
];
}, [data]);
if (loading) {
return <Loading icon="dots" className={styles.loading} />;
}
return (
<BarChart
className={className}
datasets={datasets}
unit="day"
loading={loading}
renderXLabel={renderXLabel}
renderTooltipPopup={renderTooltipPopup}
XAxisType="category"
/>
);
}
export default RetentionChart;

View file

@ -0,0 +1,3 @@
.loading {
height: 300px;
}

View file

@ -0,0 +1,44 @@
import { useContext, useRef } from 'react';
import { useMessages } from 'hooks';
import { Form, FormButtons, FormInput, FormRow, SubmitButton, TextField } from 'react-basics';
import { ReportContext } from 'components/pages/reports/Report';
import BaseParameters from '../BaseParameters';
export function RetentionParameters() {
const { report, runReport, isRunning } = useContext(ReportContext);
const { formatMessage, labels } = useMessages();
const ref = useRef(null);
const { parameters } = report || {};
const { websiteId, dateRange } = parameters || {};
const queryDisabled = !websiteId || !dateRange;
const handleSubmit = (data, e) => {
e.stopPropagation();
e.preventDefault();
if (!queryDisabled) {
runReport(data);
}
};
return (
<Form ref={ref} values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
<BaseParameters />
<FormRow label={formatMessage(labels.window)}>
<FormInput
name="window"
rules={{ required: formatMessage(labels.required), pattern: /[0-9]+/ }}
>
<TextField autoComplete="off" />
</FormInput>
</FormRow>
<FormButtons>
<SubmitButton variant="primary" disabled={queryDisabled} loading={isRunning}>
{formatMessage(labels.runQuery)}
</SubmitButton>
</FormButtons>
</Form>
);
}
export default RetentionParameters;

View file

@ -0,0 +1,28 @@
import RetentionChart from './RetentionChart';
import RetentionTable from './RetentionTable';
import RetentionParameters from './RetentionParameters';
import Report from '../Report';
import ReportHeader from '../ReportHeader';
import ReportMenu from '../ReportMenu';
import ReportBody from '../ReportBody';
import Funnel from 'assets/funnel.svg';
const defaultParameters = {
type: 'Retention',
parameters: { window: 60, urls: [] },
};
export default function RetentionReport({ reportId }) {
return (
<Report reportId={reportId} defaultParameters={defaultParameters}>
<ReportHeader icon={<Funnel />} />
<ReportMenu>
<RetentionParameters />
</ReportMenu>
<ReportBody>
<RetentionChart />
<RetentionTable />
</ReportBody>
</Report>
);
}

View file

@ -0,0 +1,10 @@
.filters {
display: flex;
flex-direction: column;
justify-content: space-between;
border: 1px solid var(--base400);
border-radius: var(--border-radius);
line-height: 32px;
padding: 10px;
overflow: hidden;
}

View file

@ -0,0 +1,19 @@
import { useContext } from 'react';
import DataTable from 'components/metrics/DataTable';
import { useMessages } from 'hooks';
import { ReportContext } from '../Report';
export function RetentionTable() {
const { report } = useContext(ReportContext);
const { formatMessage, labels } = useMessages();
return (
<DataTable
data={report?.data}
title={formatMessage(labels.url)}
metric={formatMessage(labels.visitors)}
showPercentage={true}
/>
);
}
export default RetentionTable;