+
+
- {counter} unique visitors last 5 minutes
+ {counter} unique visitors last 5 minutes
+ Click to see activity for the last 30 minutes
);
diff --git a/apps/web/src/components/overview/overview-filters-buttons.tsx b/apps/web/src/components/overview/overview-filters-buttons.tsx
index c21b1664..d089258b 100644
--- a/apps/web/src/components/overview/overview-filters-buttons.tsx
+++ b/apps/web/src/components/overview/overview-filters-buttons.tsx
@@ -1,5 +1,6 @@
'use client';
+import { cn } from '@/utils/cn';
import { X } from 'lucide-react';
import { Button } from '../ui/button';
@@ -7,8 +8,10 @@ import { useOverviewOptions } from './useOverviewOptions';
export function OverviewFiltersButtons() {
const options = useOverviewOptions();
+ const activeFilter = options.filters.length > 0;
+
return (
- <>
+
{options.referrer && (
)}
- >
+
);
}
diff --git a/apps/web/src/components/overview/overview-live-histogram.tsx b/apps/web/src/components/overview/overview-live-histogram.tsx
new file mode 100644
index 00000000..d47f18a7
--- /dev/null
+++ b/apps/web/src/components/overview/overview-live-histogram.tsx
@@ -0,0 +1,68 @@
+'use client';
+
+import type { IChartInput } from '@/types';
+import { cn } from '@/utils/cn';
+import { ChevronsUpDownIcon } from 'lucide-react';
+import AnimateHeight from 'react-animate-height';
+
+import { Chart } from '../report/chart';
+import { Widget, WidgetBody, WidgetHead } from '../Widget';
+import { useOverviewOptions } from './useOverviewOptions';
+
+interface OverviewLiveHistogramProps {
+ projectId: string;
+}
+export function OverviewLiveHistogram({
+ projectId,
+}: OverviewLiveHistogramProps) {
+ const { liveHistogram, setLiveHistogram } = useOverviewOptions();
+ const report: IChartInput = {
+ projectId,
+ events: [
+ {
+ segment: 'user',
+ filters: [
+ {
+ id: '1',
+ name: 'name',
+ operator: 'is',
+ value: ['screen_view', 'session_start'],
+ },
+ ],
+ id: 'A',
+ name: '*',
+ displayName: 'Active users',
+ },
+ ],
+ chartType: 'histogram',
+ interval: 'minute',
+ range: '30min',
+ name: '',
+ metric: 'sum',
+ breakdowns: [],
+ lineType: 'monotone',
+ previous: true,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/components/overview/overview-top-devices.tsx b/apps/web/src/components/overview/overview-top-devices.tsx
index 1d4a1987..525cee7f 100644
--- a/apps/web/src/components/overview/overview-top-devices.tsx
+++ b/apps/web/src/components/overview/overview-top-devices.tsx
@@ -1,8 +1,6 @@
'use client';
-import { Suspense } from 'react';
import { Chart } from '@/components/report/chart';
-import { ChartLoading } from '@/components/report/chart/ChartLoading';
import { cn } from '@/utils/cn';
import { Widget, WidgetBody } from '../Widget';
@@ -25,6 +23,7 @@ export default function OverviewTopDevices({
setBrowserVersion,
setOS,
setOSVersion,
+ setDevice,
} = useOverviewOptions();
const [widget, setWidget, widgets] = useOverviewWidget('tech', {
devices: {
@@ -187,31 +186,32 @@ export default function OverviewTopDevices({
- }>
- {
- switch (widget.key) {
- case 'browser':
- setWidget('browser_version');
- setBrowser(item.name);
- break;
- case 'browser_version':
- setBrowserVersion(item.name);
- break;
- case 'os':
- setWidget('os_version');
- setOS(item.name);
- break;
- case 'os_version':
- setOSVersion(item.name);
- break;
- }
- }}
- />
-
+ {
+ switch (widget.key) {
+ case 'devices':
+ setDevice(item.name);
+ break;
+ case 'browser':
+ setWidget('browser_version');
+ setBrowser(item.name);
+ break;
+ case 'browser_version':
+ setBrowserVersion(item.name);
+ break;
+ case 'os':
+ setWidget('os_version');
+ setOS(item.name);
+ break;
+ case 'os_version':
+ setOSVersion(item.name);
+ break;
+ }
+ }}
+ />
>
diff --git a/apps/web/src/components/overview/overview-top-events.tsx b/apps/web/src/components/overview/overview-top-events.tsx
index 07cb4431..b9ef4e5a 100644
--- a/apps/web/src/components/overview/overview-top-events.tsx
+++ b/apps/web/src/components/overview/overview-top-events.tsx
@@ -74,9 +74,7 @@ export default function OverviewTopEvents({
- }>
-
-
+
>
diff --git a/apps/web/src/components/overview/overview-top-geo.tsx b/apps/web/src/components/overview/overview-top-geo.tsx
index 59e70084..b6bdd992 100644
--- a/apps/web/src/components/overview/overview-top-geo.tsx
+++ b/apps/web/src/components/overview/overview-top-geo.tsx
@@ -149,28 +149,26 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
- }>
- {
- switch (widget.key) {
- case 'countries':
- setWidget('regions');
- setCountry(item.name);
- break;
- case 'regions':
- setWidget('cities');
- setRegion(item.name);
- break;
- case 'cities':
- setCity(item.name);
- break;
- }
- }}
- />
-
+ {
+ switch (widget.key) {
+ case 'countries':
+ setWidget('regions');
+ setCountry(item.name);
+ break;
+ case 'regions':
+ setWidget('cities');
+ setRegion(item.name);
+ break;
+ case 'cities':
+ setCity(item.name);
+ break;
+ }
+ }}
+ />
>
diff --git a/apps/web/src/components/overview/overview-top-pages.tsx b/apps/web/src/components/overview/overview-top-pages.tsx
index d5539c52..59b79c72 100644
--- a/apps/web/src/components/overview/overview-top-pages.tsx
+++ b/apps/web/src/components/overview/overview-top-pages.tsx
@@ -120,16 +120,14 @@ export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
- }>
- {
- setPage(item.name);
- }}
- />
-
+ {
+ setPage(item.name);
+ }}
+ />
>
diff --git a/apps/web/src/components/overview/overview-top-sources.tsx b/apps/web/src/components/overview/overview-top-sources.tsx
index 905142f7..e798dd39 100644
--- a/apps/web/src/components/overview/overview-top-sources.tsx
+++ b/apps/web/src/components/overview/overview-top-sources.tsx
@@ -275,43 +275,41 @@ export default function OverviewTopSources({
- }>
- {
- switch (widget.key) {
- case 'all':
- setReferrerName(item.name);
- setWidget('domain');
- break;
- case 'domain':
- setReferrer(item.name);
- break;
- case 'type':
- setReferrerType(item.name);
- setWidget('domain');
- break;
- case 'utm_source':
- setUtmSource(item.name);
- break;
- case 'utm_medium':
- setUtmMedium(item.name);
- break;
- case 'utm_campaign':
- setUtmCampaign(item.name);
- break;
- case 'utm_term':
- setUtmTerm(item.name);
- break;
- case 'utm_content':
- setUtmContent(item.name);
- break;
- }
- }}
- />
-
+ {
+ switch (widget.key) {
+ case 'all':
+ setReferrerName(item.name);
+ setWidget('domain');
+ break;
+ case 'domain':
+ setReferrer(item.name);
+ break;
+ case 'type':
+ setReferrerType(item.name);
+ setWidget('domain');
+ break;
+ case 'utm_source':
+ setUtmSource(item.name);
+ break;
+ case 'utm_medium':
+ setUtmMedium(item.name);
+ break;
+ case 'utm_campaign':
+ setUtmCampaign(item.name);
+ break;
+ case 'utm_term':
+ setUtmTerm(item.name);
+ break;
+ case 'utm_content':
+ setUtmContent(item.name);
+ break;
+ }
+ }}
+ />
>
diff --git a/apps/web/src/components/overview/overview-widget.tsx b/apps/web/src/components/overview/overview-widget.tsx
index 3be1dc73..92a8ba6a 100644
--- a/apps/web/src/components/overview/overview-widget.tsx
+++ b/apps/web/src/components/overview/overview-widget.tsx
@@ -1,9 +1,8 @@
'use client';
-import { Children, useCallback, useEffect, useRef, useState } from 'react';
+import { Children, useEffect, useRef, useState } from 'react';
import { useThrottle } from '@/hooks/useThrottle';
import { cn } from '@/utils/cn';
-import throttle from 'lodash.throttle';
import { ChevronsUpDownIcon } from 'lucide-react';
import { last } from 'ramda';
diff --git a/apps/web/src/components/overview/useOverviewOptions.ts b/apps/web/src/components/overview/useOverviewOptions.ts
index c1400630..a5e73df7 100644
--- a/apps/web/src/components/overview/useOverviewOptions.ts
+++ b/apps/web/src/components/overview/useOverviewOptions.ts
@@ -107,6 +107,12 @@ export function useOverviewOptions() {
parseAsString.withOptions(nuqsOptions)
);
+ // Toggles
+ const [liveHistogram, setLiveHistogram] = useQueryState(
+ 'live',
+ parseAsBoolean.withDefault(false).withOptions(nuqsOptions)
+ );
+
const filters = useMemo(() => {
const filters: IChartInput['events'][number]['filters'] = [];
@@ -337,5 +343,9 @@ export function useOverviewOptions() {
setOS,
osVersion,
setOSVersion,
+
+ // Toggles
+ liveHistogram,
+ setLiveHistogram,
};
}
diff --git a/apps/web/src/components/report/chart/ChartProvider.tsx b/apps/web/src/components/report/chart/ChartProvider.tsx
index 5f26c859..e852f44a 100644
--- a/apps/web/src/components/report/chart/ChartProvider.tsx
+++ b/apps/web/src/components/report/chart/ChartProvider.tsx
@@ -3,6 +3,7 @@
import {
createContext,
memo,
+ Suspense,
useContext,
useEffect,
useMemo,
@@ -12,6 +13,7 @@ import type { IChartSerie } from '@/server/api/routers/chart';
import type { IChartInput } from '@/types';
import { ChartLoading } from './ChartLoading';
+import { MetricCardLoading } from './MetricCard';
export interface ChartContextType extends IChartInput {
editMode?: boolean;
@@ -47,10 +49,10 @@ export function ChartProvider({
({
+ ...props,
editMode: editMode ?? false,
previous: previous ?? false,
hideID: hideID ?? false,
- ...props,
}),
[editMode, previous, hideID, props]
)}
@@ -64,20 +66,34 @@ export function withChartProivder(
WrappedComponent: React.FC
) {
const WithChartProvider = (props: ComponentProps & ChartContextType) => {
- const [mounted, setMounted] = useState(props.chartType === 'metric');
+ const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
- return ;
+ return props.chartType === 'metric' ? (
+
+ ) : (
+
+ );
}
return (
-
-
-
+
+ ) : (
+
+ )
+ }
+ >
+
+
+
+
);
};
diff --git a/apps/web/src/components/report/chart/LazyChart.tsx b/apps/web/src/components/report/chart/LazyChart.tsx
index 5aa2672c..2fd3cb89 100644
--- a/apps/web/src/components/report/chart/LazyChart.tsx
+++ b/apps/web/src/components/report/chart/LazyChart.tsx
@@ -23,13 +23,11 @@ export function LazyChart(props: ReportChartProps & ChartContextType) {
return (
- }>
- {once.current || inViewport ? (
-
- ) : (
-
- )}
-
+ {once.current || inViewport ? (
+
+ ) : (
+
+ )}
);
}
diff --git a/apps/web/src/components/report/chart/ReportAreaChart.tsx b/apps/web/src/components/report/chart/ReportAreaChart.tsx
index 09162bc9..2f5ca221 100644
--- a/apps/web/src/components/report/chart/ReportAreaChart.tsx
+++ b/apps/web/src/components/report/chart/ReportAreaChart.tsx
@@ -20,6 +20,7 @@ import { getYAxisWidth } from './chart-utils';
import { useChartContext } from './ChartProvider';
import { ReportChartTooltip } from './ReportChartTooltip';
import { ReportTable } from './ReportTable';
+import { ResponsiveContainer } from './ResponsiveContainer';
interface ReportAreaChartProps {
data: IChartData;
@@ -39,83 +40,72 @@ export function ReportAreaChart({
return (
<>
-
-
- {({ width }) => (
-
- } />
- formatDate(m)}
- tickLine={false}
- allowDuplicatedCategory={false}
- />
-
+
+ {({ width, height }) => (
+
+ } />
+ formatDate(m)}
+ tickLine={false}
+ allowDuplicatedCategory={false}
+ />
+
- {series.map((serie) => {
- const color = getChartColor(serie.index);
- return (
-
-
-
-
-
-
-
-
-
- );
- })}
-
-
- )}
-
-
+ {series.map((serie) => {
+ const color = getChartColor(serie.index);
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+ )}
+
{editMode && (
state.report.interval);
const formatDate = useFormatDateInterval(interval);
const number = useNumber();
if (!active || !payload) {
diff --git a/apps/web/src/components/report/chart/ReportHistogramChart.tsx b/apps/web/src/components/report/chart/ReportHistogramChart.tsx
index 080bb19a..50a63125 100644
--- a/apps/web/src/components/report/chart/ReportHistogramChart.tsx
+++ b/apps/web/src/components/report/chart/ReportHistogramChart.tsx
@@ -13,6 +13,7 @@ import { getYAxisWidth } from './chart-utils';
import { useChartContext } from './ChartProvider';
import { ReportChartTooltip } from './ReportChartTooltip';
import { ReportTable } from './ReportTable';
+import { ResponsiveContainer } from './ResponsiveContainer';
interface ReportHistogramChartProps {
data: IChartData;
@@ -43,61 +44,52 @@ export function ReportHistogramChart({
return (
<>
-
-
- {({ width }) => (
-
-
- } cursor={} />
-
-
- {series.map((serie) => {
- return (
-
- {previous && (
-
- )}
+
+ {({ width, height }) => (
+
+
+ } cursor={} />
+
+
+ {series.map((serie) => {
+ return (
+
+ {previous && (
-
- );
- })}
-
- )}
-
-
+ )}
+
+
+ );
+ })}
+
+ )}
+
{editMode && (
-
-
- {({ width }) => (
-
-
-
- } />
- formatDate(m)}
- tickLine={false}
- allowDuplicatedCategory={false}
- />
- {series.map((serie) => {
- return (
-
+
+ {({ width, height }) => (
+
+
+
+ } />
+ formatDate(m)}
+ tickLine={false}
+ allowDuplicatedCategory={false}
+ />
+ {series.map((serie) => {
+ return (
+
+
+ {previous && (
- {previous && (
-
- )}
-
- );
- })}
-
- )}
-
-
+ )}
+
+ );
+ })}
+
+ )}
+
{editMode && (
React.ReactNode;
+}
+
+export function ResponsiveContainer({ children }: ResponsiveContainerProps) {
+ const { editMode } = useChartContext();
+ const maxHeight = 300;
+ const minHeight = 200;
+ return (
+
+
+ {({ width }) =>
+ children({
+ width,
+ height: Math.min(
+ Math.max(width * 0.5625, minHeight),
+ // we add p-4 (16px) padding in edit mode
+ editMode ? maxHeight - 16 : maxHeight
+ ),
+ })
+ }
+
+
+ );
+}
diff --git a/apps/web/src/components/report/chart/SerieIcon.tsx b/apps/web/src/components/report/chart/SerieIcon.tsx
index 3ccead30..62e811b1 100644
--- a/apps/web/src/components/report/chart/SerieIcon.tsx
+++ b/apps/web/src/components/report/chart/SerieIcon.tsx
@@ -60,9 +60,9 @@ export function SerieIcon({ name, ...props }: SerieIconProps) {
)) as LucideIcon;
}
- return (
+ return Icon ? (
- {Icon ? : null}
+
- );
+ ) : null;
}
diff --git a/apps/web/src/components/report/chart/index.tsx b/apps/web/src/components/report/chart/index.tsx
index 41206b7b..a8334c68 100644
--- a/apps/web/src/components/report/chart/index.tsx
+++ b/apps/web/src/components/report/chart/index.tsx
@@ -20,80 +20,78 @@ export type ReportChartProps = IChartInput & {
initialData?: RouterOutputs['chart']['chart'];
};
-export const Chart = memo(
- withChartProivder(function Chart({
- interval,
- events,
- breakdowns,
- chartType,
- name,
- range,
- lineType,
- previous,
- formula,
- unit,
- metric,
- projectId,
- }: ReportChartProps) {
- const [data] = api.chart.chart.useSuspenseQuery(
- {
- // dont send lineType since it does not need to be sent
- lineType: 'monotone',
- interval,
- chartType,
- events,
- breakdowns,
- name,
- range,
- startDate: null,
- endDate: null,
- projectId,
- previous,
- formula,
- unit,
- metric,
- },
- {
- keepPreviousData: true,
- }
+export const Chart = withChartProivder(function Chart({
+ interval,
+ events,
+ breakdowns,
+ chartType,
+ name,
+ range,
+ lineType,
+ previous,
+ formula,
+ unit,
+ metric,
+ projectId,
+}: ReportChartProps) {
+ const [data] = api.chart.chart.useSuspenseQuery(
+ {
+ // dont send lineType since it does not need to be sent
+ lineType: 'monotone',
+ interval,
+ chartType,
+ events,
+ breakdowns,
+ name,
+ range,
+ startDate: null,
+ endDate: null,
+ projectId,
+ previous,
+ formula,
+ unit,
+ metric,
+ },
+ {
+ keepPreviousData: true,
+ }
+ );
+
+ if (data.series.length === 0) {
+ return ;
+ }
+
+ if (chartType === 'map') {
+ return ;
+ }
+
+ if (chartType === 'histogram') {
+ return ;
+ }
+
+ if (chartType === 'bar') {
+ return ;
+ }
+
+ if (chartType === 'metric') {
+ return ;
+ }
+
+ if (chartType === 'pie') {
+ return ;
+ }
+
+ if (chartType === 'linear') {
+ return (
+
);
+ }
- if (data.series.length === 0) {
- return ;
- }
+ if (chartType === 'area') {
+ return (
+
+ );
+ }
- if (chartType === 'map') {
- return ;
- }
-
- if (chartType === 'histogram') {
- return ;
- }
-
- if (chartType === 'bar') {
- return ;
- }
-
- if (chartType === 'metric') {
- return ;
- }
-
- if (chartType === 'pie') {
- return ;
- }
-
- if (chartType === 'linear') {
- return (
-
- );
- }
-
- if (chartType === 'area') {
- return (
-
- );
- }
-
- return Unknown chart type
;
- })
-);
+ return Unknown chart type
;
+});
diff --git a/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx b/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx
index 9f2e7a5a..ed21b2e6 100644
--- a/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx
+++ b/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx
@@ -1,11 +1,11 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
+import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import type { IChartEvent } from '@/types';
import { cn } from '@/utils/cn';
import { DatabaseIcon } from 'lucide-react';
-import { useChartContext } from '../chart/ChartProvider';
import { changeEvent } from '../reportSlice';
interface EventPropertiesComboboxProps {
@@ -16,7 +16,7 @@ export function EventPropertiesCombobox({
event,
}: EventPropertiesComboboxProps) {
const dispatch = useDispatch();
- const { projectId } = useChartContext();
+ const { projectId } = useAppParams();
const query = api.chart.properties.useQuery(
{
diff --git a/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx b/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx
index fb8e9efc..dcbb63fc 100644
--- a/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx
+++ b/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx
@@ -3,17 +3,17 @@
import { api } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
import { Combobox } from '@/components/ui/combobox';
+import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux';
import type { IChartBreakdown } from '@/types';
import { SplitIcon } from 'lucide-react';
-import { useChartContext } from '../chart/ChartProvider';
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
import { ReportBreakdownMore } from './ReportBreakdownMore';
import type { ReportEventMoreProps } from './ReportEventMore';
export function ReportBreakdowns() {
- const { projectId } = useChartContext();
+ const { projectId } = useAppParams();
const selectedBreakdowns = useSelector((state) => state.report.breakdowns);
const dispatch = useDispatch();
const propertiesQuery = api.chart.properties.useQuery({
diff --git a/apps/web/src/components/report/sidebar/ReportEvents.tsx b/apps/web/src/components/report/sidebar/ReportEvents.tsx
index 3a460d49..ea258b92 100644
--- a/apps/web/src/components/report/sidebar/ReportEvents.tsx
+++ b/apps/web/src/components/report/sidebar/ReportEvents.tsx
@@ -6,12 +6,12 @@ import { Dropdown } from '@/components/Dropdown';
import { Checkbox } from '@/components/ui/checkbox';
import { Combobox } from '@/components/ui/combobox';
import { Input } from '@/components/ui/input';
+import { useAppParams } from '@/hooks/useAppParams';
import { useDebounceFn } from '@/hooks/useDebounceFn';
import { useDispatch, useSelector } from '@/redux';
import type { IChartEvent } from '@/types';
import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
-import { useChartContext } from '../chart/ChartProvider';
import {
addEvent,
changeEvent,
@@ -28,7 +28,8 @@ export function ReportEvents() {
const previous = useSelector((state) => state.report.previous);
const selectedEvents = useSelector((state) => state.report.events);
const dispatch = useDispatch();
- const { projectId } = useChartContext();
+ const { projectId } = useAppParams();
+
const eventsQuery = api.chart.events.useQuery({
projectId,
});
diff --git a/apps/web/src/components/report/sidebar/filters/FilterItem.tsx b/apps/web/src/components/report/sidebar/filters/FilterItem.tsx
index 011b0b77..8e457836 100644
--- a/apps/web/src/components/report/sidebar/filters/FilterItem.tsx
+++ b/apps/web/src/components/report/sidebar/filters/FilterItem.tsx
@@ -4,6 +4,7 @@ import { Dropdown } from '@/components/Dropdown';
import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { RenderDots } from '@/components/ui/RenderDots';
+import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings';
import { useDispatch } from '@/redux';
import type {
@@ -14,7 +15,6 @@ import type {
import { operators } from '@/utils/constants';
import { SlidersHorizontal, Trash } from 'lucide-react';
-import { useChartContext } from '../../chart/ChartProvider';
import { changeEvent } from '../../reportSlice';
interface FilterProps {
@@ -23,7 +23,7 @@ interface FilterProps {
}
export function FilterItem({ filter, event }: FilterProps) {
- const { projectId } = useChartContext();
+ const { projectId } = useAppParams();
const getLabel = useMappings();
const dispatch = useDispatch();
const potentialValues = api.chart.values.useQuery({
diff --git a/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx b/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx
index 0423ec8e..e639b61c 100644
--- a/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx
+++ b/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx
@@ -1,10 +1,10 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
+import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import type { IChartEvent } from '@/types';
import { FilterIcon } from 'lucide-react';
-import { useChartContext } from '../../chart/ChartProvider';
import { changeEvent } from '../../reportSlice';
interface FiltersComboboxProps {
@@ -13,7 +13,7 @@ interface FiltersComboboxProps {
export function FiltersCombobox({ event }: FiltersComboboxProps) {
const dispatch = useDispatch();
- const { projectId } = useChartContext();
+ const { projectId } = useAppParams();
const query = api.chart.properties.useQuery(
{
diff --git a/apps/web/src/hooks/useVisibleSeries.ts b/apps/web/src/hooks/useVisibleSeries.ts
index 49ef5938..0fb0b209 100644
--- a/apps/web/src/hooks/useVisibleSeries.ts
+++ b/apps/web/src/hooks/useVisibleSeries.ts
@@ -1,6 +1,6 @@
'use client';
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import type { IChartData } from '@/app/_trpc/client';
export type IVisibleSeries = ReturnType['series'];
@@ -10,6 +10,12 @@ export function useVisibleSeries(data: IChartData, limit?: number | undefined) {
data?.series?.slice(0, max).map((serie) => serie.name) ?? []
);
+ useEffect(() => {
+ setVisibleSeries(
+ data?.series?.slice(0, max).map((serie) => serie.name) ?? []
+ );
+ }, [data, max]);
+
return useMemo(() => {
return {
series: data.series
diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts
index 8a42ef2c..68bdefb5 100644
--- a/apps/web/src/middleware.ts
+++ b/apps/web/src/middleware.ts
@@ -4,13 +4,9 @@ import { authMiddleware } from '@clerk/nextjs';
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware
export default authMiddleware({
- publicRoutes: [
- '/share/overview/:id',
- '/api/trpc/chart.chart',
- '/api/trpc/chart.values',
- ],
+ publicRoutes: ['/share/overview/:id', '/api/trpc(.*)'],
});
export const config = {
- matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
+ matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api)(.*)'],
};
diff --git a/apps/web/src/server/api/routers/chart.helpers.ts b/apps/web/src/server/api/routers/chart.helpers.ts
index f054716f..170b58da 100644
--- a/apps/web/src/server/api/routers/chart.helpers.ts
+++ b/apps/web/src/server/api/routers/chart.helpers.ts
@@ -10,7 +10,7 @@ import { round } from '@/utils/math';
import * as mathjs from 'mathjs';
import { sort } from 'ramda';
-import { chQuery } from '@mixan/db';
+import { chQuery, convertClickhouseDateToJs } from '@mixan/db';
export type GetChartDataResult = Awaited>;
export interface ResultItem {
@@ -73,7 +73,7 @@ function fillEmptySpotsInTimeline(
const getMinute = (date: Date) => date.getUTCMinutes();
const item = items.find((item) => {
- const date = new Date(item.date);
+ const date = convertClickhouseDateToJs(item.date);
if (interval === 'month') {
return (
diff --git a/apps/web/src/server/api/routers/chart.ts b/apps/web/src/server/api/routers/chart.ts
index 2982d734..1f2f524f 100644
--- a/apps/web/src/server/api/routers/chart.ts
+++ b/apps/web/src/server/api/routers/chart.ts
@@ -10,7 +10,7 @@ import { zChartInput } from '@/utils/validation';
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
import { z } from 'zod';
-import { chQuery } from '@mixan/db';
+import { chQuery, createSqlBuilder } from '@mixan/db';
import { getChartData, withFormula } from './chart.helpers';
@@ -117,16 +117,20 @@ export const chartRouter = createTRPCRouter({
})
)
.query(async ({ input: { event, property, projectId } }) => {
- const sql = property.startsWith('properties.')
- ? `SELECT distinct mapValues(mapExtractKeyLike(properties, '${property
- .replace(/^properties\./, '')
- .replace(
- '.*.',
- '.%.'
- )}')) as values from events where name = '${event}' AND project_id = '${projectId}';`
- : `SELECT ${property} as values from events where name = '${event}' AND project_id = '${projectId}';`;
+ const { sb, getSql } = createSqlBuilder();
+ sb.where.project_id = `project_id = '${projectId}'`;
+ if (event !== '*') {
+ sb.where.event = `name = '${event}'`;
+ }
+ if (property.startsWith('properties.')) {
+ sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, '${property
+ .replace(/^properties\./, '')
+ .replace('.*.', '.%.')}')) as values`;
+ } else {
+ sb.select.values = `${property} as values`;
+ }
- const events = await chQuery<{ values: string[] }>(sql);
+ const events = await chQuery<{ values: string[] }>(getSql());
const values = pipe(
(data: typeof events) => map(prop('values'), data),
diff --git a/packages/db/src/clickhouse-client.ts b/packages/db/src/clickhouse-client.ts
index 051bbb29..65946db0 100644
--- a/packages/db/src/clickhouse-client.ts
+++ b/packages/db/src/clickhouse-client.ts
@@ -49,3 +49,7 @@ export function formatClickhouseDate(_date: Date | string) {
const date = typeof _date === 'string' ? new Date(_date) : _date;
return date.toISOString().replace('T', ' ').replace(/Z+$/, '');
}
+
+export function convertClickhouseDateToJs(date: string) {
+ return new Date(date.replace(' ', 'T') + 'Z');
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 394f5efd..60149b1e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4036,7 +4036,7 @@ packages:
resolution: {integrity: sha512-bOhuFnlRaS7CU33+rFFIWdcET/Vkyn1vsN8BYFwCDEF5P1fVVvYN7bFOsQLTMD3nvi35C1AGmtqUr/Wfv8Xaow==}
engines: {node: '>=12'}
dependencies:
- '@expo/spawn-async': 1.5.0
+ '@expo/spawn-async': 1.7.2
exec-async: 2.2.0
dev: false
@@ -4044,7 +4044,7 @@ packages:
resolution: {integrity: sha512-LKdo/6y4W7llZ6ghsg1kdx2CeH/qR/c6QI/JI8oPUvppsZoeIYjSkdflce978fAMfR8IXoi0wt0jA2w0kWpwbg==}
dependencies:
'@expo/json-file': 8.3.0
- '@expo/spawn-async': 1.5.0
+ '@expo/spawn-async': 1.7.2
ansi-regex: 5.0.1
chalk: 4.1.2
find-up: 5.0.0
@@ -8908,7 +8908,7 @@ packages:
engines: {node: '>=12.13.0'}
hasBin: true
dependencies:
- '@types/node': 20.11.17
+ '@types/node': 18.19.15
escape-string-regexp: 4.0.0
is-wsl: 2.2.0
lighthouse-logger: 1.4.2
@@ -8919,7 +8919,7 @@ packages:
/chromium-edge-launcher@1.0.0:
resolution: {integrity: sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==}
dependencies:
- '@types/node': 20.11.17
+ '@types/node': 18.19.15
escape-string-regexp: 4.0.0
is-wsl: 2.2.0
lighthouse-logger: 1.4.2