Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor data fetching to use browser timezone, remove server rendering #110

Merged
merged 12 commits into from
Nov 15, 2024
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 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 @@
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 @@
.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 All @@ -45,13 +49,13 @@
if (dataFetcher.state === "idle") {
loadData();
}
}, []);

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

View workflow job for this annotation

GitHub Actions / test

React Hook useEffect has missing dependencies: 'dataFetcher.state' and 'loadData'. Either include them or remove the dependency array

useEffect(() => {
if (dataFetcher.state === "idle") {
loadData();
}
}, [siteId, interval, filters]);

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

View workflow job for this annotation

GitHub Actions / test

React Hook useEffect has missing dependencies: 'dataFetcher.state' and 'loadData'. Either include them or remove the dependency array

function handlePagination(page: number) {
loadData(page.toString());
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 @@
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 @@
// 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}`
);

Check warning on line 63 in app/components/TimeSeriesChart.tsx

View check run for this annotation

Codecov / codecov/patch

app/components/TimeSeriesChart.tsx#L55-L63

Added lines #L55 - L63 were not covered by tests
}

return (
Expand Down Expand Up @@ -97,4 +100,5 @@
}).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 @@

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";
}

Check warning on line 59 in app/lib/utils.ts

View check run for this annotation

Codecov / codecov/patch

app/lib/utils.ts#L57-L59

Added lines #L57 - L59 were not covered by tests
}

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

Check warning on line 67 in app/lib/utils.ts

View check run for this annotation

Codecov / codecov/patch

app/lib/utils.ts#L67

Added line #L67 was not covered by tests
case "7d":
case "30d":
case "90d":
return "DAY";
default:
return "DAY";

Check warning on line 73 in app/lib/utils.ts

View check run for this annotation

Codecov / codecov/patch

app/lib/utils.ts#L73

Added line #L73 was not covered by tests
}
}

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(),
};
}

Check warning on line 110 in app/lib/utils.ts

View check run for this annotation

Codecov / codecov/patch

app/lib/utils.ts#L78-L110

Added lines #L78 - L110 were not covered by tests
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
Loading