Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
_site
*.local
data
local
39 changes: 39 additions & 0 deletions src/components/daily-registrations-report.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from "chai";
import { yearMonthDayParse } from "../formats";
import { ProcessedResult } from "../models/daily-registrations-report-data";
import { tabulate } from "./daily-registrations-report";

describe("DailyRegistrationsReport", () => {
describe("#tabulate", () => {
it("renders a table", () => {
const results: ProcessedResult[] = [
{
date: yearMonthDayParse("2020-01-01"),
totalUsers: 5,
fullyRegisteredUsers: 1,
totalUsersCumulative: 10,
fullyRegisteredUsersCumulative: 2,
},
{
date: yearMonthDayParse("2020-01-02"),
totalUsers: 6,
fullyRegisteredUsers: 2,
totalUsersCumulative: 17,
fullyRegisteredUsersCumulative: 4,
},
];

const table = tabulate(results);

expect(table).to.deep.equal({
header: ["", "2020-01-01", "2020-01-02"],
body: [
["New Users", 5, 6],
["New Fully Registered Users", 1, 2],
["Cumulative Users", 10, 17],
["Cumulative Fully Registered Users", 2, 4],
],
});
});
});
});
116 changes: 116 additions & 0 deletions src/components/daily-registrations-report.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { VNode } from "preact";
import { useQuery } from "preact-fetching";
import { useRef, useState, useContext } from "preact/hooks";
import * as Plot from "@observablehq/plot";
import { ReportFilterContext } from "../contexts/report-filter-context";
import useResizeListener from "../hooks/resize-listener";
import {
DataType,
loadData,
ProcessedResult,
ProcessedRenderableData,
toRenderableData,
} from "../models/daily-registrations-report-data";
import PlotComponent from "./plot";
import { formatSIDropTrailingZeroes, formatWithCommas, yearMonthDayFormat } from "../formats";
import Table, { TableData } from "./table";

function plot({
data,
width,
}: {
data: ProcessedRenderableData[];
width?: number;
}): HTMLElement {
return Plot.plot({
color: {
legend: true,
type: "ordinal",
tickFormat: (type: DataType): string => {
switch (type) {
case DataType.TOTAL_USERS:
case DataType.TOTAL_USERS_CUMULATIVE:
return "Total Users";
case DataType.FULLY_REGISTERED_USERS:
case DataType.FULLY_REGISTERED_USERS_CUMULATIVE:
return "Full Registered Users";
default:
throw new Error(`Unknown type ${type}`);
}
},
},
marks: [
Plot.ruleY([0]),
Plot.line(data, {
x: "date",
y: "value",
z: "type",
stroke: "type",
}),
],
width,
y: {
tickFormat: formatSIDropTrailingZeroes,
},
});
}

/**
* Assumes that results is pre-sorted, pre-filtered
*/
function tabulate(results: ProcessedResult[]): TableData {
return {
header: ["", ...results.map(({ date }) => yearMonthDayFormat(date))],
body: [
["New Users", ...results.map(({ totalUsers }) => totalUsers)],
[
"New Fully Registered Users",
...results.map(({ fullyRegisteredUsers }) => fullyRegisteredUsers),
],
["Cumulative Users", ...results.map(({ totalUsersCumulative }) => totalUsersCumulative)],
[
"Cumulative Fully Registered Users",
...results.map(({ fullyRegisteredUsersCumulative }) => fullyRegisteredUsersCumulative),
],
],
};
}

function DailyRegistrationsReport(): VNode {
const ref = useRef(null as HTMLDivElement | null);
const [width, setWidth] = useState(undefined as number | undefined);
useResizeListener(() => setWidth(ref.current?.offsetWidth));
const { start, finish, env, cumulative } = useContext(ReportFilterContext);

const { data } = useQuery(`daily-registrations-${finish.valueOf()}`, () => loadData(finish, env));

const filteredData = data && toRenderableData(data).filter(({ type }) => {
switch (type) {
case DataType.TOTAL_USERS:
case DataType.FULLY_REGISTERED_USERS:
return !cumulative;
case DataType.TOTAL_USERS_CUMULATIVE:
case DataType.FULLY_REGISTERED_USERS_CUMULATIVE:
return !!cumulative;
default:
throw new Error(`Unknown data type ${type}`);
}
});

const windowedData = data && data.filter(({ date }) => start <= date && date <= finish);

return (
<div ref={ref}>
{filteredData && (
<PlotComponent
plotter={() => plot({ data: filteredData, width })}
inputs={[data, width, cumulative]}
/>
)}
{windowedData && <Table numberFormatter={formatWithCommas} data={tabulate(windowedData)} />}
</div>
);
}

export default DailyRegistrationsReport;
export { tabulate };
Loading