Skip to content

Commit

Permalink
chore: refactor logic to retrieve analytics metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
mbhrznr committed Jun 28, 2023
1 parent 929899f commit 3c3c5da
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 37 deletions.
11 changes: 6 additions & 5 deletions routes/stats.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import type { Handlers, PageProps } from "$fresh/server.ts";
import { DAY } from "std/datetime/constants.ts";
import { SITE_WIDTH_STYLES } from "@/utils/constants.ts";
import Head from "@/components/Head.tsx";
import type { State } from "./_middleware.ts";
import { getManyAnalyticsMetricsPerDay } from "@/utils/db.ts";
import { getManyAnalyticsMetricsSince } from "@/utils/db.ts";
import { Chart } from "fresh_charts/mod.ts";
import { ChartColors } from "fresh_charts/utils.ts";

Expand All @@ -20,17 +21,17 @@ interface StatsPageData extends State {
export const handler: Handlers<StatsPageData, State> = {
async GET(_, ctx) {
const daysBefore = 30;

const metricsKeys = [
"visits_count",
"users_count",
"items_count",
"votes_count",
];
const metricsTitles = ["Visits", "New Users", "New Items", "New Votes"];
const metricsByDay = await getManyAnalyticsMetricsPerDay(metricsKeys, {
limit: daysBefore,
});
const metricsByDay = await getManyAnalyticsMetricsSince(
metricsKeys,
daysBefore * DAY,
);

return ctx.render({ ...ctx.state, metricsByDay, metricsTitles });
},
Expand Down
52 changes: 25 additions & 27 deletions utils/db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { DAY } from "https://deno.land/std@0.192.0/datetime/constants.ts";

const KV_PATH_KEY = "KV_PATH";
let path = undefined;
if (
Expand Down Expand Up @@ -30,6 +28,20 @@ async function getValues<T>(
return values;
}

/** Gets all dates since a given number of milliseconds ago */
export function getDatesSince(msAgo: number) {
const dates = [];
const now = Date.now();
const start = new Date(now - msAgo);

while (+start < now) {
start.setDate(start.getDate() + 1);
dates.push(formatDate(new Date(start)));
}

return dates;
}

/** Converts `Date` to ISO format that is zero UTC offset */
export function formatDate(date: Date) {
return date.toISOString().split("T")[0];
Expand Down Expand Up @@ -350,7 +362,6 @@ export async function updateUser(user: User) {
const usersByLoginKey = ["users_by_login", user.login];
const usersBySessionKey = ["users_by_session", user.sessionId];

<<<<<<< HEAD
const atomicOp = kv.atomic();

if (user.stripeCustomerId !== undefined) {
Expand All @@ -363,9 +374,6 @@ export async function updateUser(user: User) {
}

const res = await atomicOp
=======
const res = await kv.atomic()
>>>>>>> e6ae61b (chore: revert unintended formatting)
.set(usersKey, user)
.set(usersByLoginKey, user)
.set(usersBySessionKey, user)
Expand Down Expand Up @@ -506,37 +514,27 @@ export async function getAllUsersCountByDay(options?: Deno.KvListOptions) {
return { visits, dates };
}

export async function getAnalyticsMetricListPerDay(
export async function getAnalyticsMetricsSince(
metric: string,
options?: Deno.KvListOptions,
msAgo: number,
) {
const limit = options?.limit ?? 0;
const metricsMap = new Map<string, number>(
Array.from({ length: limit })
.map<[string, number]>((_, index) => [
new Date(Date.now() - DAY * index).toISOString().split("T")[0],
0,
])
.sort((a, b) => a[0].localeCompare(b[0])),
);
const iter = kv.list<bigint>({ prefix: [metric] }, options);
for await (const res of iter) {
if (metricsMap.has(String(res.key[1]))) {
metricsMap.set(String(res.key[1]), Number(res.value));
}
const dates = getDatesSince(msAgo);
const keys = dates.map((date) => [metric, date]);
const metricsValue = [];
for await (const key of keys) {
const value = await getValue<number>(key);
metricsValue.push(Number(value ?? 0));
}
const dates = Array.from(metricsMap.keys());
const metricsValue = Array.from(metricsMap.values());

return { dates, metricsValue };
}

export async function getManyAnalyticsMetricsPerDay(
export async function getManyAnalyticsMetricsSince(
metrics: string[],
options?: Deno.KvListOptions,
msAgo: number,
) {
const analyticsByDay = await Promise.all(
metrics.map((metric) => getAnalyticsMetricListPerDay(metric, options)),
metrics.map((metric) => getAnalyticsMetricsSince(metric, msAgo)),
);

return analyticsByDay;
Expand Down
22 changes: 17 additions & 5 deletions utils/db_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import {
deleteVote,
formatDate,
getAllItems,
getAnalyticsMetricsSince,
getCommentsByItem,
getDatesSince,
getItem,
getItemsByUser,
getItemsCountByDay,
getItemsSince,
getManyAnalyticsMetricsSince,
getManyUsers,
getUser,
getUserByLogin,
Expand Down Expand Up @@ -73,6 +76,20 @@ function genNewUser(): User {
};
}

Deno.test("[db] formatDate()", () => {
assertEquals(formatDate(new Date("2023-01-01")), "2023-01-01");
assertEquals(formatDate(new Date("2023-01-01T13:59:08.740Z")), "2023-01-01");
});

Deno.test("[db] getDatesSince()", () => {
assertEquals(getDatesSince(0), []);
assertEquals(getDatesSince(DAY), [formatDate(new Date())]);
assertEquals(getDatesSince(DAY * 2), [
formatDate(new Date(Date.now() - DAY)),
formatDate(new Date()),
]);
});

Deno.test("[db] newItemProps()", () => {
const itemProps = newItemProps();
assertAlmostEquals(itemProps.createdAt.getTime(), Date.now(), 1e-6);
Expand Down Expand Up @@ -235,8 +252,3 @@ Deno.test("[db] votes", async () => {
await deleteVote({ item, user });
assertRejects(async () => await deleteVote({ item, user }));
});

Deno.test("[db] formatDate()", () => {
assertEquals(formatDate(new Date("2023-01-01")), "2023-01-01");
assertEquals(formatDate(new Date("2023-01-01T13:59:08.740Z")), "2023-01-01");
});

0 comments on commit 3c3c5da

Please sign in to comment.