Skip to content

Commit

Permalink
Defer fetching filter options until they're selected
Browse files Browse the repository at this point in the history
  • Loading branch information
TWilson023 committed May 31, 2024
1 parent d6e95d1 commit 4b5ba73
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 40 deletions.
1 change: 1 addition & 0 deletions apps/web/ui/analytics/analytics-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function AnalyticsCard<T extends string>({
<div className="flex gap-2">
{eventTabs.map(({ id, icon: Icon, label }) => (
<button
key={id}
onClick={() => setEventTab(id)}
title={label}
className={cn(
Expand Down
81 changes: 53 additions & 28 deletions apps/web/ui/analytics/toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
linkConstructor,
nFormatter,
} from "@dub/utils";
import { useContext, useMemo } from "react";
import { useCallback, useContext, useMemo, useState } from "react";
import { AnalyticsContext } from ".";
import LinkLogo from "../links/link-logo";
import { COLORS_LIST } from "../links/tag-badge";
Expand All @@ -52,24 +52,62 @@ import { useAnalyticsFilterOption } from "./utils";

export default function Toggle() {
const { plan } = useWorkspace();
const { queryParams, searchParamsObj } = useRouterStuff();
const { basePath, domain, key, url, admin, demo, start, end, interval } =
useContext(AnalyticsContext);

const isPublicStatsPage = basePath.startsWith("/stats");

const scrolled = useScroll(80);

const { tags } = useTags();
const { allDomains: domains } = useDomains();

const countries = useAnalyticsFilterOption("countries");
const cities = useAnalyticsFilterOption("cities");
const devices = useAnalyticsFilterOption("devices");
const browsers = useAnalyticsFilterOption("browsers");
const os = useAnalyticsFilterOption("os");
const links = useAnalyticsFilterOption("top_links");
const [requestedFilters, setRequestedFilters] = useState<string[]>([]);

const { queryParams, searchParamsObj } = useRouterStuff();
const activeFilters = useMemo(() => {
const { domain, tagId, qr, country, city, device, browser, os, key } =
searchParamsObj;
return [
...(domain && !key ? [{ key: "domain", value: domain }] : []),
...(domain && key
? [{ key: "link", value: linkConstructor({ domain, key }) }]
: []),
...(tagId ? [{ key: "tagId", value: tagId }] : []),
...(qr ? [{ key: "qr", value: qr === "true" }] : []),
...(country ? [{ key: "country", value: country }] : []),
...(city ? [{ key: "city", value: city }] : []),
...(device ? [{ key: "device", value: device }] : []),
...(browser ? [{ key: "browser", value: browser }] : []),
...(os ? [{ key: "os", value: os }] : []),
];
}, [searchParamsObj]);

const scrolled = useScroll(80);
const isEnabled = useCallback(
(key: string) =>
requestedFilters.includes(key) ||
activeFilters.some((af) => af.key === key),
[requestedFilters, activeFilters],
);

const isPublicStatsPage = basePath.startsWith("/stats");
const links = useAnalyticsFilterOption("top_links", {
enabled: isEnabled("link"),
});
const countries = useAnalyticsFilterOption("countries", {
enabled: isEnabled("country"),
});
const cities = useAnalyticsFilterOption("cities", {
enabled: isEnabled("city"),
});
const devices = useAnalyticsFilterOption("devices", {
enabled: isEnabled("device"),
});
const browsers = useAnalyticsFilterOption("browsers", {
enabled: isEnabled("browser"),
});
const os = useAnalyticsFilterOption("os", {
enabled: isEnabled("os"),
});

const filters = useMemo(
() => [
Expand Down Expand Up @@ -231,24 +269,6 @@ export default function Toggle() {
[domains, links, tags, countries, cities, devices, browsers, os],
);

const activeFilters = useMemo(() => {
const { domain, tagId, qr, country, city, device, browser, os, key } =
searchParamsObj;
return [
...(domain && !key ? [{ key: "domain", value: domain }] : []),
...(domain && key
? [{ key: "link", value: linkConstructor({ domain, key }) }]
: []),
...(tagId ? [{ key: "tagId", value: tagId }] : []),
...(qr ? [{ key: "qr", value: qr === "true" }] : []),
...(country ? [{ key: "country", value: country }] : []),
...(city ? [{ key: "city", value: city }] : []),
...(device ? [{ key: "device", value: device }] : []),
...(browser ? [{ key: "browser", value: browser }] : []),
...(os ? [{ key: "os", value: os }] : []),
];
}, [searchParamsObj]);

return (
<>
<div
Expand Down Expand Up @@ -341,6 +361,11 @@ export default function Toggle() {
del: key === "link" ? ["domain", "key"] : key,
})
}
onOpenFilter={(key) =>
setRequestedFilters((rf) =>
rf.includes(key) ? rf : [...rf, key],
)
}
/>
)}
<div
Expand Down
3 changes: 2 additions & 1 deletion apps/web/ui/analytics/top-links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default function TopLinks() {
const { domain, key } = useContext(AnalyticsContext);
const showUrls = domain && key;

const data = useAnalyticsFilterOption(`top_${showUrls ? "urls" : "links"}`, {
const data = useAnalyticsFilterOption({
groupBy: `top_${showUrls ? "urls" : "links"}`,
root: selectedTabId === "domains" ? "true" : "false",
});

Expand Down
23 changes: 17 additions & 6 deletions apps/web/ui/analytics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@ import { useContext } from "react";
import useSWR from "swr";
import { AnalyticsContext } from ".";

/**
* Fetches event counts grouped by the specified filter
*
* @param groupByOrParams Either a groupBy option or a query parameter object including groupBy
* @param options Additional options
*/
export function useAnalyticsFilterOption(
groupBy: AnalyticsGroupByOptions,
additionalParams?: Record<string, any>,
groupByOrParams:
| AnalyticsGroupByOptions
| ({ groupBy: AnalyticsGroupByOptions } & Record<string, any>),
options?: { enabled?: boolean },
): ({ count?: number } & Record<string, any>)[] | null {
const { baseApiPath, queryString, selectedTab, requiresUpgrade } =
useContext(AnalyticsContext);

const { data } = useSWR<Record<string, any>[]>(
`${baseApiPath}?${editQueryString(queryString, {
groupBy,
...additionalParams,
})}`,
options?.enabled !== false
? `${baseApiPath}?${editQueryString(queryString, {
...(typeof groupByOrParams === "string"
? { groupBy: groupByOrParams }
: groupByOrParams),
})}`
: null,
fetcher,
{
shouldRetryOnError: !requiresUpgrade,
Expand Down
11 changes: 7 additions & 4 deletions packages/ui/src/filter/filter-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type FilterSelectProps = {
filters: Filter[];
onSelect: (key: string, value: string) => void;
onRemove: (key: string) => void;
onOpenFilter?: (key: string) => void;
activeFilters?: {
key: Filter["key"];
value: FilterOption["value"];
Expand All @@ -35,6 +36,7 @@ export function FilterSelect({
filters,
onSelect,
onRemove,
onOpenFilter,
activeFilters,
children,
className,
Expand Down Expand Up @@ -79,6 +81,7 @@ export function FilterSelect({

setSearch("");
setSelectedFilterKey(key);
onOpenFilter?.(key);
}, []);

const selectOption = useCallback(
Expand Down Expand Up @@ -129,7 +132,7 @@ export function FilterSelect({
/>
<FilterScroll key={selectedFilterKey} ref={mainListContainer}>
{!selectedFilter ? (
<Command.List className="flex w-full min-w-[160px] flex-col gap-1">
<Command.List className="flex w-full min-w-[160px] flex-col gap-1 p-2">
{filters.map((filter) => (
<FilterButton
{...filter}
Expand All @@ -142,7 +145,7 @@ export function FilterSelect({
) : (
<Command.List className="flex w-full min-w-[100px] flex-col gap-1">
{selectedFilter.options ? (
<>
<div className="p-2">
{selectedFilter.options?.map((option) => {
const isSelected =
activeFilters?.find(
Expand All @@ -165,7 +168,7 @@ export function FilterSelect({
);
})}
<NoMatches />
</>
</div>
) : (
<Command.Loading>
<div
Expand Down Expand Up @@ -237,7 +240,7 @@ const FilterScroll = forwardRef(
return (
<>
<div
className="scrollbar-hide max-h-[50vh] w-screen overflow-y-scroll p-2 sm:w-auto"
className="scrollbar-hide max-h-[50vh] w-screen overflow-y-scroll sm:w-auto"
ref={ref}
onScroll={updateScrollProgress}
>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/tab-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function TabSelect<T extends string>({
<div className="flex text-sm">
<LayoutGroup id={layoutGroupId}>
{options.map(({ id, label }) => (
<div className="relative">
<div key={id} className="relative">
<button
type="button"
onClick={() => onSelect?.(id)}
Expand Down

0 comments on commit 4b5ba73

Please sign in to comment.