mirror of
https://github.com/umami-software/umami.git
synced 2026-02-10 15:47:13 +01:00
Fixed retention report showing wrong dates. Changed Breakdown field select to modal.
This commit is contained in:
parent
ee8750d9df
commit
ea83afbc13
20 changed files with 108 additions and 277 deletions
|
|
@ -24,7 +24,7 @@ import { Panel } from '@/components/common/Panel';
|
||||||
import { DateDisplay } from '@/components/common/DateDisplay';
|
import { DateDisplay } from '@/components/common/DateDisplay';
|
||||||
|
|
||||||
const views = {
|
const views = {
|
||||||
url: PagesTable,
|
path: PagesTable,
|
||||||
title: PagesTable,
|
title: PagesTable,
|
||||||
referrer: ReferrersTable,
|
referrer: ReferrersTable,
|
||||||
browser: BrowsersTable,
|
browser: BrowsersTable,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export function WebsiteControls({
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<Row alignItems="center" justifyContent="space-between" gap="3">
|
<Row alignItems="center" justifyContent="space-between" gap="3">
|
||||||
{allowFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
{allowFilter ? <WebsiteFilterButton websiteId={websiteId} /> : <div />}
|
||||||
{allowDateFilter && <WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />}
|
{allowDateFilter && <WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />}
|
||||||
{allowMonthFilter && <WebsiteMonthSelect websiteId={websiteId} />}
|
{allowMonthFilter && <WebsiteMonthSelect websiteId={websiteId} />}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ export function WebsiteMetricsBar({
|
||||||
const metrics = data
|
const metrics = data
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: pageviews,
|
value: visitors,
|
||||||
label: formatMessage(labels.views),
|
label: formatMessage(labels.visitors),
|
||||||
change: pageviews - previous.pageviews,
|
change: visitors - previous.visitors,
|
||||||
formatValue: formatLongNumber,
|
formatValue: formatLongNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -42,9 +42,9 @@ export function WebsiteMetricsBar({
|
||||||
formatValue: formatLongNumber,
|
formatValue: formatLongNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: visitors,
|
value: pageviews,
|
||||||
label: formatMessage(labels.visitors),
|
label: formatMessage(labels.views),
|
||||||
change: visitors - previous.visitors,
|
change: pageviews - previous.pageviews,
|
||||||
formatValue: formatLongNumber,
|
formatValue: formatLongNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,14 @@ export function Breakdown({ websiteId, parameters, startDate, endDate }: Breakdo
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<DataColumn id="views" label={formatMessage(labels.views)} align="end">
|
<DataColumn id="visitors" label={formatMessage(labels.visitors)} align="end">
|
||||||
{row => row?.['views']?.toLocaleString()}
|
{row => row?.['visitors']?.toLocaleString()}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end">
|
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end">
|
||||||
{row => row?.['visits']?.toLocaleString()}
|
{row => row?.['visits']?.toLocaleString()}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
<DataColumn id="visitors" label={formatMessage(labels.visitors)} align="end">
|
<DataColumn id="views" label={formatMessage(labels.views)} align="end">
|
||||||
{row => row?.['visitors']?.toLocaleString()}
|
{row => row?.['views']?.toLocaleString()}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
<DataColumn id="bounceRate" label={formatMessage(labels.bounceRate)} align="end">
|
<DataColumn id="bounceRate" label={formatMessage(labels.bounceRate)} align="end">
|
||||||
{row => {
|
{row => {
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,12 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import { Button, Column, Box, Text, Icon, DialogTrigger, Modal, Dialog } from '@umami/react-zen';
|
||||||
List,
|
import { useDateRange, useMessages } from '@/components/hooks';
|
||||||
ListItem,
|
import { ListCheck } from '@/components/icons';
|
||||||
Button,
|
|
||||||
Column,
|
|
||||||
Box,
|
|
||||||
Grid,
|
|
||||||
Text,
|
|
||||||
Icon,
|
|
||||||
Popover,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '@umami/react-zen';
|
|
||||||
import { useDateRange, useMessages, useFields } from '@/components/hooks';
|
|
||||||
import { SquarePlus, Chevron } from '@/components/icons';
|
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { Breakdown } from './Breakdown';
|
import { Breakdown } from './Breakdown';
|
||||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||||
|
import { FieldSelectForm } from '@/app/(main)/websites/[websiteId]/reports/breakdown/FieldSelectForm';
|
||||||
|
|
||||||
export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||||
const {
|
const {
|
||||||
|
|
@ -27,9 +17,7 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<WebsiteControls websiteId={websiteId} />
|
<WebsiteControls websiteId={websiteId} />
|
||||||
<Box>
|
<FieldsButton value={fields} onChange={setFields} />
|
||||||
<FieldsButton value={fields} onChange={setFields} />
|
|
||||||
</Box>
|
|
||||||
<Panel height="900px" overflow="auto" allowFullscreen>
|
<Panel height="900px" overflow="auto" allowFullscreen>
|
||||||
<Breakdown
|
<Breakdown
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
|
|
@ -43,55 +31,25 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const FieldsButton = ({ value, onChange }) => {
|
const FieldsButton = ({ value, onChange }) => {
|
||||||
const [selected, setSelected] = useState(value);
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { fields } = useFields();
|
|
||||||
|
|
||||||
const handleChange = value => {
|
|
||||||
setSelected(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleApply = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
onChange?.(selected);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
setSelected(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogTrigger>
|
<Box>
|
||||||
<Button variant="quiet" onPress={() => setIsOpen(!isOpen)}>
|
<DialogTrigger>
|
||||||
<Icon>
|
<Button>
|
||||||
<SquarePlus />
|
<Icon>
|
||||||
</Icon>
|
<ListCheck />
|
||||||
<Text>Fields</Text>
|
</Icon>
|
||||||
<Icon rotate={90}>
|
<Text>Fields</Text>
|
||||||
<Chevron />
|
</Button>
|
||||||
</Icon>
|
<Modal>
|
||||||
</Button>
|
<Dialog title={formatMessage(labels.fields)}>
|
||||||
<Popover placement="bottom start" isOpen={isOpen}>
|
{({ close }) => (
|
||||||
<Column width="300px" padding="2" border borderRadius shadow="3" backgroundColor gap>
|
<FieldSelectForm selectedFields={value} onChange={onChange} onClose={close} />
|
||||||
<List value={selected} onChange={handleChange} selectionMode="multiple">
|
)}
|
||||||
{fields.map(({ name, label }) => {
|
</Dialog>
|
||||||
return (
|
</Modal>
|
||||||
<ListItem key={name} id={name}>
|
</DialogTrigger>
|
||||||
{label}
|
</Box>
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
<Grid columns="1fr 1fr" gap>
|
|
||||||
<Button onPress={handleClose}>{formatMessage(labels.cancel)}</Button>
|
|
||||||
<Button onPress={handleApply} variant="primary">
|
|
||||||
{formatMessage(labels.apply)}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Column>
|
|
||||||
</Popover>
|
|
||||||
</DialogTrigger>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Column, List, ListItem, Grid, Button } from '@umami/react-zen';
|
||||||
|
import { useFields, useMessages } from '@/components/hooks';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export function FieldSelectForm({
|
||||||
|
selectedFields = [],
|
||||||
|
onChange,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
selectedFields?: string[];
|
||||||
|
onChange: (values: string[]) => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}) {
|
||||||
|
const [selected, setSelected] = useState(selectedFields);
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { fields } = useFields();
|
||||||
|
|
||||||
|
const handleChange = (value: string[]) => {
|
||||||
|
setSelected(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApply = () => {
|
||||||
|
onChange?.(selected);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column width="300px" gap="6">
|
||||||
|
<List value={selected} onChange={handleChange} selectionMode="multiple">
|
||||||
|
{fields.map(({ name, label }) => {
|
||||||
|
return (
|
||||||
|
<ListItem key={name} id={name}>
|
||||||
|
{label}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
<Grid columns="1fr 1fr" gap>
|
||||||
|
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||||
|
<Button onPress={handleApply} variant="primary">
|
||||||
|
{formatMessage(labels.apply)}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -69,7 +69,7 @@ export function GoalEditForm({
|
||||||
label={formatMessage(labels.name)}
|
label={formatMessage(labels.name)}
|
||||||
rules={{ required: formatMessage(labels.required) }}
|
rules={{ required: formatMessage(labels.required) }}
|
||||||
>
|
>
|
||||||
<TextField />
|
<TextField autoFocus />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField
|
<FormField
|
||||||
name="type"
|
name="type"
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
|
||||||
const { timezone } = useTimezone();
|
const { timezone } = useTimezone();
|
||||||
const { data, error, isLoading } = useResultQuery<any>('retention', {
|
const { data, error, isLoading } = useResultQuery<any>('retention', {
|
||||||
websiteId,
|
websiteId,
|
||||||
timezone,
|
|
||||||
dateRange: {
|
dateRange: {
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
timezone,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export async function POST(request: Request) {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: { startDate, endDate },
|
dateRange: { startDate, endDate },
|
||||||
parameters: { fields },
|
parameters: { fields },
|
||||||
|
filters,
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
|
|
@ -24,6 +25,7 @@ export async function POST(request: Request) {
|
||||||
const data = await getBreakdown(websiteId, fields, {
|
const data = await getBreakdown(websiteId, fields, {
|
||||||
startDate: new Date(startDate),
|
startDate: new Date(startDate),
|
||||||
endDate: new Date(endDate),
|
endDate: new Date(endDate),
|
||||||
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return json(data);
|
return json(data);
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ export async function POST(request: Request) {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
websiteId,
|
websiteId,
|
||||||
dateRange: { startDate, endDate },
|
dateRange: { startDate, endDate, timezone },
|
||||||
timezone,
|
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
.bar {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--base600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link span {
|
|
||||||
color: var(--base700) !important;
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { Fragment } from 'react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { Row, Icon, Text } from '@umami/react-zen';
|
|
||||||
import { Chevron } from '@/components/icons';
|
|
||||||
import styles from './Breadcrumb.module.css';
|
|
||||||
|
|
||||||
export interface BreadcrumbProps {
|
|
||||||
data: {
|
|
||||||
url?: string;
|
|
||||||
label: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Breadcrumb({ data }: BreadcrumbProps) {
|
|
||||||
return (
|
|
||||||
<Row alignItems="center" gap className={styles.bar}>
|
|
||||||
{data.map((a, i) => {
|
|
||||||
return (
|
|
||||||
<Fragment key={i}>
|
|
||||||
{a.url ? (
|
|
||||||
<Link href={a.url} className={styles.link}>
|
|
||||||
<Text>{a.label}</Text>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Text>{a.label}</Text>
|
|
||||||
)}
|
|
||||||
{i !== data.length - 1 ? (
|
|
||||||
<Icon rotate={270}>
|
|
||||||
<Chevron />
|
|
||||||
</Icon>
|
|
||||||
) : null}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
.search {
|
|
||||||
max-width: 300px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body td {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
min-height: 70px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body > div > div > div {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pager {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
.button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
align-self: flex-start;
|
|
||||||
white-space: nowrap;
|
|
||||||
gap: var(--size200);
|
|
||||||
font-family: inherit;
|
|
||||||
color: var(--base900);
|
|
||||||
background: var(--base100);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
min-height: var(--base-height);
|
|
||||||
padding: 0 var(--size600);
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
background: var(--base200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:active {
|
|
||||||
background: var(--base300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:visited {
|
|
||||||
color: var(--base900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.disabled {
|
|
||||||
color: var(--disabled-color) !important;
|
|
||||||
background-color: var(--disabled-background) !important;
|
|
||||||
border-color: transparent !important;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.primary {
|
|
||||||
color: var(--light50);
|
|
||||||
background: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.primary:hover {
|
|
||||||
color: var(--light50);
|
|
||||||
background: var(--primary500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.primary:active {
|
|
||||||
color: var(--light50);
|
|
||||||
background: var(--primary600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.secondary {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
background: var(--base50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.secondary:hover {
|
|
||||||
background: var(--base75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.secondary:active {
|
|
||||||
background: var(--base100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.quiet {
|
|
||||||
color: var(--base900);
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.quiet:hover {
|
|
||||||
background: var(--base100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.quiet:active {
|
|
||||||
background: var(--base200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.danger {
|
|
||||||
color: var(--light50);
|
|
||||||
background: var(--red800);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.danger:hover {
|
|
||||||
color: var(--light50);
|
|
||||||
background: var(--red900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.danger:active {
|
|
||||||
color: var(--light50);
|
|
||||||
background: var(--red1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.size-sm {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
height: calc(var(--base-height) * 0.75);
|
|
||||||
padding: 0 calc(var(--size600) * 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.size-md {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.size-lg {
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
height: calc(var(--base-height) * 1.25);
|
|
||||||
padding: 0 calc(var(--size600) * 1.25);
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useApi } from '@/components/hooks';
|
import { useApi } from '../useApi';
|
||||||
|
import { useFilterParams } from '../useFilterParams';
|
||||||
import { ReactQueryOptions } from '@/lib/types';
|
import { ReactQueryOptions } from '@/lib/types';
|
||||||
|
|
||||||
export function useResultQuery<T>(
|
export function useResultQuery<T>(
|
||||||
|
|
@ -6,11 +7,21 @@ export function useResultQuery<T>(
|
||||||
params?: { [key: string]: any },
|
params?: { [key: string]: any },
|
||||||
options?: ReactQueryOptions<T>,
|
options?: ReactQueryOptions<T>,
|
||||||
) {
|
) {
|
||||||
|
const { websiteId } = params;
|
||||||
const { post, useQuery } = useApi();
|
const { post, useQuery } = useApi();
|
||||||
|
const filterParams = useFilterParams(websiteId);
|
||||||
|
|
||||||
return useQuery<T>({
|
return useQuery<T>({
|
||||||
queryKey: ['reports', type, params],
|
queryKey: [
|
||||||
queryFn: () => post(`/reports/${type}`, { type, ...params }),
|
'reports',
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
websiteId,
|
||||||
|
...filterParams,
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
queryFn: () => post(`/reports/${type}`, { type, ...filterParams, ...params }),
|
||||||
enabled: !!type,
|
enabled: !!type,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export type WebsiteMetricsData = {
|
||||||
|
|
||||||
export function useWebsiteMetricsQuery(
|
export function useWebsiteMetricsQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
queryParams: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
|
params: { type: string; limit?: number; search?: string; startAt?: number; endAt?: number },
|
||||||
options?: ReactQueryOptions<WebsiteMetricsData>,
|
options?: ReactQueryOptions<WebsiteMetricsData>,
|
||||||
) {
|
) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
|
|
@ -24,14 +24,14 @@ export function useWebsiteMetricsQuery(
|
||||||
{
|
{
|
||||||
websiteId,
|
websiteId,
|
||||||
...filterParams,
|
...filterParams,
|
||||||
...queryParams,
|
...params,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
get(`/websites/${websiteId}/metrics`, {
|
get(`/websites/${websiteId}/metrics`, {
|
||||||
...filterParams,
|
...filterParams,
|
||||||
[searchParams.get('view')]: undefined,
|
[searchParams.get('view')]: undefined,
|
||||||
...queryParams,
|
...params,
|
||||||
}),
|
}),
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export {
|
||||||
Download,
|
Download,
|
||||||
Edit,
|
Edit,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
|
Equal,
|
||||||
Eye,
|
Eye,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
File,
|
File,
|
||||||
|
|
@ -20,6 +21,7 @@ export {
|
||||||
KeyRound,
|
KeyRound,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Link,
|
Link,
|
||||||
|
ListCheck,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
LockKeyhole,
|
LockKeyhole,
|
||||||
LogOut,
|
LogOut,
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ export const reportParms = {
|
||||||
endDate: z.coerce.date(),
|
endDate: z.coerce.date(),
|
||||||
num: z.coerce.number().optional(),
|
num: z.coerce.number().optional(),
|
||||||
offset: z.coerce.number().optional(),
|
offset: z.coerce.number().optional(),
|
||||||
unit: z.string().optional(),
|
timezone: timezoneParam.optional(),
|
||||||
|
unit: unitParam.optional(),
|
||||||
value: z.string().optional(),
|
value: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ export interface QueryFilters {
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
eventType?: number;
|
eventType?: number;
|
||||||
url?: string;
|
path?: string;
|
||||||
referrer?: string;
|
referrer?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
query?: string;
|
query?: string;
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ async function clickhouseQuery(
|
||||||
user_activities AS (
|
user_activities AS (
|
||||||
select distinct
|
select distinct
|
||||||
w.session_id,
|
w.session_id,
|
||||||
(${getDateSQL('created_at', unit)} - c.cohort_date) / 86400 as day_number
|
(${getDateSQL('created_at', unit, timezone)} - c.cohort_date) / 86400 as day_number
|
||||||
from website_event w
|
from website_event w
|
||||||
join cohort_items c
|
join cohort_items c
|
||||||
on w.session_id = c.session_id
|
on w.session_id = c.session_id
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue