Skip to content

Commit

Permalink
Refactor data fetching to use browser timezone, remove server renderi…
Browse files Browse the repository at this point in the history
…ng (#110)

* Begin refactoring date/interval code into utils.ts

* Time series chart renders

* Add timezone to time series chart

* Sites ordered by hits doesnt need tz

* Update resources.country to use client tz

* Cards all pass timezone

* Fix errant cf timezone in resources.paths.tsx

* Reload timeseries chart on interval change

* Refactor stats into its own card, remove all uses of cf timezone

* Fix tests

* Fix lint errors

* Fix "unknown" site id issue
  • Loading branch information
benvinegar authored Nov 15, 2024
1 parent 56518f8 commit 0a19c38
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 204 deletions.
7 changes: 2 additions & 5 deletions app/analytics/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,15 +600,12 @@ export class AnalyticsEngineAPI {
);
}

async getSitesOrderedByHits(interval: string, tz?: string, limit?: number) {
async getSitesOrderedByHits(interval: string, limit?: number) {
// defaults to 1 day if not specified

limit = limit || 10;

const { startIntervalSql, endIntervalSql } = intervalToSql(
interval,
tz,
);
const { startIntervalSql, endIntervalSql } = intervalToSql(interval);

const query = `
SELECT SUM(_sample_interval) as count,
Expand Down
24 changes: 14 additions & 10 deletions app/components/PaginatedTableCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import { Card } from "./ui/card";
import PaginationButtons from "./PaginationButtons";
import { SearchFilters } from "~/lib/types";

interface PaginatedTableCardProps {
siteId: string;
interval: string;
dataFetcher: any;

Check warning on line 11 in app/components/PaginatedTableCard.tsx

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
columnHeaders: string[];
filters?: SearchFilters;
loaderUrl: string;
onClick?: (key: string) => void;
timezone?: string;
}

const PaginatedTableCard = ({
siteId,
interval,
Expand All @@ -13,15 +24,8 @@ const PaginatedTableCard = ({
filters,
loaderUrl,
onClick,
}: {
siteId: string;
interval: string;
dataFetcher: any; // ignore type for now
columnHeaders: string[];
filters?: SearchFilters;
loaderUrl: string;
onClick?: (key: string) => void;
}) => {
timezone,
}: PaginatedTableCardProps) => {
const countsByProperty = dataFetcher.data?.countsByProperty || [];
const page = dataFetcher.data?.page || 1;

Expand All @@ -33,7 +37,7 @@ const PaginatedTableCard = ({
.join("")
: "";

let url = `${loaderUrl}?site=${siteId}&interval=${interval}${filterString}`;
let url = `${loaderUrl}?site=${siteId}&interval=${interval}&timezone=${timezone}${filterString}`;
if (page) {
url += `&page=${page}`;
}
Expand Down
18 changes: 11 additions & 7 deletions app/components/TimeSeriesChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
export default function TimeSeriesChart({
data,
intervalType,
timezone,
}: InferProps<typeof TimeSeriesChart.propTypes>) {
// chart doesn't really work no data points, so just bail out
if (data.length === 0) {
Expand Down Expand Up @@ -51,13 +52,15 @@ export default function TimeSeriesChart({
// convert from utc to local time
dateObj.setMinutes(dateObj.getMinutes() - dateObj.getTimezoneOffset());

return dateObj.toLocaleString("en-us", {
weekday: "short",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
});
return (
dateObj.toLocaleString("en-us", {
weekday: "short",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
}) + ` ${timezone}`
);
}

return (
Expand Down Expand Up @@ -97,4 +100,5 @@ TimeSeriesChart.propTypes = {
}).isRequired,
).isRequired,
intervalType: PropTypes.string,
timezone: PropTypes.string,
};
65 changes: 65 additions & 0 deletions app/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";

dayjs.extend(utc);
dayjs.extend(timezone);

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
Expand Down Expand Up @@ -43,3 +49,62 @@ export function getFiltersFromSearchParams(searchParams: URLSearchParams) {

return filters;
}

export function getUserTimezone(): string {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch (e) {
// Fallback to UTC if browser doesn't support Intl API
return "UTC";
}
}

export function getIntervalType(interval: string): "DAY" | "HOUR" {
switch (interval) {
case "today":
case "yesterday":
case "1d":
return "HOUR";
case "7d":
case "30d":
case "90d":
return "DAY";
default:
return "DAY";
}
}

export function getDateTimeRange(interval: string, tz: string) {
let localDateTime = dayjs().utc();
let localEndDateTime: dayjs.Dayjs | undefined;

if (interval === "today") {
localDateTime = localDateTime.tz(tz).startOf("day");
} else if (interval === "yesterday") {
localDateTime = localDateTime.tz(tz).startOf("day").subtract(1, "day");
localEndDateTime = localDateTime.endOf("day").add(2, "ms");
} else {
const daysAgo = Number(interval.split("d")[0]);
const intervalType = getIntervalType(interval);

if (intervalType === "DAY") {
localDateTime = localDateTime
.subtract(daysAgo, "day")
.tz(tz)
.startOf("day");
} else if (intervalType === "HOUR") {
localDateTime = localDateTime
.subtract(daysAgo, "day")
.startOf("hour");
}
}

if (!localEndDateTime) {
localEndDateTime = dayjs().utc().tz(tz);
}

return {
startDate: localDateTime.toDate(),
endDate: localEndDateTime.toDate(),
};
}
80 changes: 32 additions & 48 deletions app/routes/__tests__/dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,6 @@ describe("Dashboard route", () => {
}),
);

// response for get counts
fetch.mockResolvedValueOnce(
createFetchResponse({
data: [
{ isVisit: 1, isVisitor: 1, count: 1 },
{ isVisit: 1, isVisitor: 0, count: 2 },
{ isVisit: 0, isVisitor: 0, count: 3 },
],
}),
);

// response for getViewsGroupedByInterval
fetch.mockResolvedValueOnce(
createFetchResponse({
data: [{ bucket: "2024-01-11 05:00:00", count: 4 }],
}),
);

vi.setSystemTime(new Date("2024-01-18T09:33:02").getTime());

const response = await loader({
Expand All @@ -149,19 +131,6 @@ describe("Dashboard route", () => {
filters: {},
siteId: "test-siteid",
sites: ["test-siteid"],
views: 6,
visits: 3,
visitors: 1,
viewsGroupedByInterval: [
["2024-01-11 05:00:00", 4],
["2024-01-12 05:00:00", 0],
["2024-01-13 05:00:00", 0],
["2024-01-14 05:00:00", 0],
["2024-01-15 05:00:00", 0],
["2024-01-16 05:00:00", 0],
["2024-01-17 05:00:00", 0],
["2024-01-18 05:00:00", 0],
],
intervalType: "DAY",
interval: "7d",
});
Expand All @@ -188,19 +157,6 @@ describe("Dashboard route", () => {
filters: {},
siteId: "",
sites: [],
views: 0,
visits: 0,
visitors: 0,
viewsGroupedByInterval: [
["2024-01-11 05:00:00", 0],
["2024-01-12 05:00:00", 0],
["2024-01-13 05:00:00", 0],
["2024-01-14 05:00:00", 0],
["2024-01-15 05:00:00", 0],
["2024-01-16 05:00:00", 0],
["2024-01-17 05:00:00", 0],
["2024-01-18 05:00:00", 0],
],
intervalType: "DAY",
interval: "7d",
});
Expand All @@ -212,10 +168,6 @@ describe("Dashboard route", () => {
return json({
siteId: "@unknown",
sites: [],
views: [],
visits: [],
visitors: [],
viewsGroupedByInterval: [],
intervalType: "day",
});
}
Expand All @@ -226,6 +178,22 @@ describe("Dashboard route", () => {
Component: Dashboard,
loader,
children: [
{
path: "/resources/timeseries",
loader: () => {
return json({ chartData: [] });
},
},
{
path: "/resources/stats",
loader: () => {
return json({
views: 0,
visits: 0,
visitors: 0,
});
},
},
{
path: "/resources/paths",
loader: () => {
Expand Down Expand Up @@ -303,6 +271,22 @@ describe("Dashboard route", () => {
Component: Dashboard,
loader,
children: [
{
path: "/resources/stats",
loader: () => {
return json({
views: 2133,
visits: 80,
visitors: 33,
});
},
},
{
path: "/resources/timeseries",
loader: () => {
return json({});
},
},
{
path: "/resources/paths",
loader: () => {
Expand Down
Loading

0 comments on commit 0a19c38

Please sign in to comment.