diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
index 01d7728f..19b3a876 100644
--- a/apps/dashboard/package.json
+++ b/apps/dashboard/package.json
@@ -70,6 +70,7 @@
"input-otp": "^1.2.4",
"javascript-time-ago": "^2.5.9",
"lodash.debounce": "^4.0.8",
+ "lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"lottie-react": "^2.4.0",
"lucide-react": "^0.331.0",
@@ -117,6 +118,7 @@
"@openpanel/tsconfig": "workspace:*",
"@types/bcrypt": "^5.0.2",
"@types/lodash.debounce": "^4.0.9",
+ "@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^18.19.15",
"@types/ramda": "^0.29.10",
diff --git a/apps/dashboard/src/components/overview/filters/origin-filter.tsx b/apps/dashboard/src/components/overview/filters/origin-filter.tsx
new file mode 100644
index 00000000..d957f632
--- /dev/null
+++ b/apps/dashboard/src/components/overview/filters/origin-filter.tsx
@@ -0,0 +1,45 @@
+import { Button } from '@/components/ui/button';
+import { useAppParams } from '@/hooks/useAppParams';
+import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
+import { api } from '@/trpc/client';
+import { cn } from '@/utils/cn';
+import { GlobeIcon } from 'lucide-react';
+
+export function OriginFilter() {
+ const { projectId } = useAppParams();
+ const [filters, setFilter] = useEventQueryFilters();
+ const originFilter = filters.find((item) => item.name === 'origin');
+
+ const { data } = api.event.origin.useQuery(
+ {
+ projectId: projectId,
+ },
+ {
+ staleTime: 1000 * 60 * 60,
+ }
+ );
+
+ if (!data || data.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {data?.map((item) => {
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/apps/dashboard/src/components/overview/filters/overview-filters-drawer-content.tsx b/apps/dashboard/src/components/overview/filters/overview-filters-drawer-content.tsx
index bd6fb227..348c68ef 100644
--- a/apps/dashboard/src/components/overview/filters/overview-filters-drawer-content.tsx
+++ b/apps/dashboard/src/components/overview/filters/overview-filters-drawer-content.tsx
@@ -12,7 +12,7 @@ import {
import { useProfileProperties } from '@/hooks/useProfileProperties';
import { useProfileValues } from '@/hooks/useProfileValues';
import { usePropertyValues } from '@/hooks/usePropertyValues';
-import { XIcon } from 'lucide-react';
+import { GlobeIcon, XIcon } from 'lucide-react';
import type { Options as NuqsOptions } from 'nuqs';
import type {
@@ -22,6 +22,7 @@ import type {
} from '@openpanel/validation';
import { useOverviewOptions } from '../useOverviewOptions';
+import { OriginFilter } from './origin-filter';
export interface OverviewFiltersDrawerContentProps {
projectId: string;
@@ -52,6 +53,7 @@ export function OverviewFiltersDrawerContent({
+
{enableEventsFilter && (
- ) : (
- /* TODO: Implement profile filters */
- null
- );
+ ) : /* TODO: Implement profile filters */
+ null;
})}
diff --git a/apps/dashboard/src/components/report/chart/Chart.tsx b/apps/dashboard/src/components/report/chart/Chart.tsx
index 35fc2394..985c0932 100644
--- a/apps/dashboard/src/components/report/chart/Chart.tsx
+++ b/apps/dashboard/src/components/report/chart/Chart.tsx
@@ -3,6 +3,7 @@
import { useEffect, useMemo, useState } from 'react';
import { api } from '@/trpc/client';
import debounce from 'lodash.debounce';
+import isEqual from 'lodash.isequal';
import type { IChartProps } from '@openpanel/validation';
@@ -18,94 +19,51 @@ import { ReportPieChart } from './ReportPieChart';
export type ReportChartProps = IChartProps;
+const pluckChartContext = (context: IChartProps) => ({
+ chartType: context.chartType,
+ interval: context.interval,
+ breakdowns: context.breakdowns,
+ range: context.range,
+ previous: context.previous,
+ formula: context.formula,
+ metric: context.metric,
+ projectId: context.projectId,
+ startDate: context.startDate,
+ endDate: context.endDate,
+ limit: context.limit,
+ offset: context.offset,
+ events: context.events.map((event) => ({
+ ...event,
+ filters: event.filters?.filter((filter) => filter.value.length > 0),
+ })),
+});
+
+// TODO: Quick hack to avoid re-fetching
+// Will refactor the entire chart component soon anyway...
function useChartData() {
- const {
- interval,
- events,
- breakdowns,
- chartType,
- range,
- previous,
- formula,
- metric,
- projectId,
- startDate,
- endDate,
- limit,
- offset,
- } = useChartContext();
-
- const [debouncedParams, setDebouncedParams] = useState({
- interval,
- events,
- breakdowns,
- chartType,
- range,
- previous,
- formula,
- metric,
- projectId,
- startDate,
- endDate,
- limit,
- offset,
- });
-
- const debouncedSetParams = useMemo(
- () => debounce(setDebouncedParams, 500),
- []
- );
+ const context = useChartContext();
+ const [params, setParams] = useState(() => pluckChartContext(context));
+ const debouncedSetParams = useMemo(() => debounce(setParams, 500), []);
useEffect(() => {
- debouncedSetParams({
- interval,
- events: events.map((event) => ({
- ...event,
- filters: event.filters?.filter((filter) => filter.value.length > 0),
- })),
- breakdowns,
- chartType,
- range,
- previous,
- formula,
- metric,
- projectId,
- startDate,
- endDate,
- limit,
- offset,
- });
+ const newParams = pluckChartContext(context);
+ if (!isEqual(newParams, params)) {
+ debouncedSetParams(newParams);
+ }
return () => {
debouncedSetParams.cancel();
};
- }, [
- interval,
- events,
- breakdowns,
- chartType,
- range,
- previous,
- formula,
- metric,
- projectId,
- startDate,
- endDate,
- limit,
- offset,
- debouncedSetParams,
- ]);
+ }, [context, params, debouncedSetParams]);
- const [data] = api.chart.chart.useSuspenseQuery(debouncedParams, {
+ return api.chart.chart.useSuspenseQuery(params, {
keepPreviousData: true,
staleTime: 1000 * 60 * 1,
});
-
- return data;
}
export function Chart() {
const { chartType } = useChartContext();
- const data = useChartData();
+ const [data] = useChartData();
if (data.series.length === 0) {
return ;
diff --git a/apps/dashboard/src/components/ui/dropdown-menu.tsx b/apps/dashboard/src/components/ui/dropdown-menu.tsx
index 877ce2b8..996fcb68 100644
--- a/apps/dashboard/src/components/ui/dropdown-menu.tsx
+++ b/apps/dashboard/src/components/ui/dropdown-menu.tsx
@@ -169,7 +169,10 @@ const DropdownMenuShortcut = ({
}: React.HTMLAttributes) => {
return (
);
diff --git a/packages/trpc/src/routers/event.ts b/packages/trpc/src/routers/event.ts
index 6f39910c..05fb72ad 100644
--- a/packages/trpc/src/routers/event.ts
+++ b/packages/trpc/src/routers/event.ts
@@ -181,4 +181,24 @@ export const eventRouter = createTRPCRouter({
.query(async ({ input }) => {
return getTopPages(input);
}),
+
+ origin: protectedProcedure
+ .input(
+ z.object({
+ projectId: z.string(),
+ })
+ )
+ .query(async ({ input }) => {
+ const res = await chQuery<{ origin: string }>(
+ `SELECT DISTINCT origin FROM ${TABLE_NAMES.events} WHERE project_id = ${escape(
+ input.projectId
+ )} AND origin IS NOT NULL AND origin != '' AND toDate(created_at) > now() - INTERVAL 30 DAY ORDER BY origin ASC`
+ );
+
+ return res.sort((a, b) =>
+ a.origin
+ .replace(/https?:\/\//, '')
+ .localeCompare(b.origin.replace(/https?:\/\//, ''))
+ );
+ }),
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 72b2650f..e8c792aa 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -341,6 +341,9 @@ importers:
lodash.debounce:
specifier: ^4.0.8
version: 4.0.8
+ lodash.isequal:
+ specifier: ^4.5.0
+ version: 4.5.0
lodash.throttle:
specifier: ^4.1.1
version: 4.1.1
@@ -477,6 +480,9 @@ importers:
'@types/lodash.debounce':
specifier: ^4.0.9
version: 4.0.9
+ '@types/lodash.isequal':
+ specifier: ^4.5.8
+ version: 4.5.8
'@types/lodash.throttle':
specifier: ^4.1.9
version: 4.1.9
@@ -7701,6 +7707,12 @@ packages:
'@types/lodash': 4.14.202
dev: true
+ /@types/lodash.isequal@4.5.8:
+ resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==}
+ dependencies:
+ '@types/lodash': 4.14.202
+ dev: true
+
/@types/lodash.throttle@4.1.9:
resolution: {integrity: sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==}
dependencies:
@@ -13449,6 +13461,10 @@ packages:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
dev: false
+ /lodash.isequal@4.5.0:
+ resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
+ dev: false
+
/lodash.isinteger@4.0.4:
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
dev: false