mirror of
https://github.com/umami-software/umami.git
synced 2025-12-08 05:12:36 +01:00
Fixed editing and navigation issues.
This commit is contained in:
parent
bf6c9395c6
commit
8c26e310f7
52 changed files with 118 additions and 122 deletions
|
|
@ -199,18 +199,6 @@ export default {
|
||||||
typescript: {
|
typescript: {
|
||||||
ignoreBuildErrors: true,
|
ignoreBuildErrors: true,
|
||||||
},
|
},
|
||||||
functions: {
|
|
||||||
'app/api/**/*.js': {
|
|
||||||
maxDuration: 30,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outputFileTracing: {
|
|
||||||
include: [
|
|
||||||
'src/generated/prisma/**/*',
|
|
||||||
'node_modules/@prisma/client/**/*',
|
|
||||||
'node_modules/.prisma/client/**/*',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
async headers() {
|
async headers() {
|
||||||
return headers;
|
return headers;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Script from 'next/script';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { UpdateNotice } from './UpdateNotice';
|
import { UpdateNotice } from './UpdateNotice';
|
||||||
import { SideNav } from '@/app/(main)/SideNav';
|
import { SideNav } from '@/app/(main)/SideNav';
|
||||||
import { MenuBar } from '@/app/(main)/MenuBar';
|
import { TopNav } from '@/app/(main)/TopNav';
|
||||||
import { useLoginQuery, useConfig } from '@/components/hooks';
|
import { useLoginQuery, useConfig } from '@/components/hooks';
|
||||||
|
|
||||||
export function App({ children }) {
|
export function App({ children }) {
|
||||||
|
|
@ -35,7 +35,7 @@ export function App({ children }) {
|
||||||
<SideNav />
|
<SideNav />
|
||||||
</Column>
|
</Column>
|
||||||
<Row gridColumn="2 / 3" gridRow="1 / 2">
|
<Row gridColumn="2 / 3" gridRow="1 / 2">
|
||||||
<MenuBar />
|
<TopNav />
|
||||||
</Row>
|
</Row>
|
||||||
<Column
|
<Column
|
||||||
gridColumn="2 / 3"
|
gridColumn="2 / 3"
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { PanelLeft, Slash } from '@/components/icons';
|
import { PanelLeft, Slash } from '@/components/icons';
|
||||||
import { useNavigation, useGlobalState } from '@/components/hooks';
|
import { useNavigation, useGlobalState } from '@/components/hooks';
|
||||||
|
|
||||||
export function MenuBar() {
|
export function TopNav() {
|
||||||
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
|
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
|
||||||
const { teamId, websiteId } = useNavigation();
|
const { teamId, websiteId, pathname } = useNavigation();
|
||||||
|
const isSettings = pathname.includes('/settings');
|
||||||
const handleSelect = () => {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
|
|
@ -30,17 +29,12 @@ export function MenuBar() {
|
||||||
</Button>
|
</Button>
|
||||||
<Row alignItems="center" gap="1">
|
<Row alignItems="center" gap="1">
|
||||||
<TeamsButton />
|
<TeamsButton />
|
||||||
{websiteId && (
|
{websiteId && !isSettings && (
|
||||||
<>
|
<>
|
||||||
<Icon strokeColor="7" rotate={-25}>
|
<Icon strokeColor="7" rotate={-25}>
|
||||||
<Slash />
|
<Slash />
|
||||||
</Icon>
|
</Icon>
|
||||||
<WebsiteSelect
|
<WebsiteSelect variant="quiet" websiteId={websiteId} teamId={teamId} />
|
||||||
variant="quiet"
|
|
||||||
websiteId={websiteId}
|
|
||||||
teamId={teamId}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
@ -21,6 +21,7 @@ export function UserDeleteForm({
|
||||||
mutate(null, {
|
mutate(null, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
touch('users');
|
touch('users');
|
||||||
|
touch(`users:${userId}`);
|
||||||
onSave?.();
|
onSave?.();
|
||||||
onClose?.();
|
onClose?.();
|
||||||
},
|
},
|
||||||
|
|
@ -35,9 +36,7 @@ export function UserDeleteForm({
|
||||||
confirmLabel={formatMessage(labels.delete)}
|
confirmLabel={formatMessage(labels.delete)}
|
||||||
isDanger
|
isDanger
|
||||||
>
|
>
|
||||||
<Row gap="1">
|
<Row gap="1">{formatMessage(messages.confirmDelete, { target: username })}</Row>
|
||||||
{formatMessage(messages.confirmDelete, { target: <b key={username}>{username}</b> })}
|
|
||||||
</Row>
|
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Row, Text, Icon, DataTable, DataColumn, MenuItem, Modal } from '@umami/react-zen';
|
import { Row, Text, Icon, DataTable, DataColumn, MenuItem, Modal, Dialog } from '@umami/react-zen';
|
||||||
import { Trash, Users } from '@/components/icons';
|
import { Trash, Users } from '@/components/icons';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { Edit } from '@/components/icons';
|
import { Edit } from '@/components/icons';
|
||||||
import { MenuButton } from '@/components/input/MenuButton';
|
import { MenuButton } from '@/components/input/MenuButton';
|
||||||
import { DateDistance } from '@/components/common/DateDistance';
|
import { DateDistance } from '@/components/common/DateDistance';
|
||||||
|
import { WebsiteDeleteForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm';
|
||||||
|
|
||||||
export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [deleteUser, setDeleteUser] = useState(null);
|
const [deleteWebsite, setDeleteWebsite] = useState(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -64,7 +65,7 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
id="delete"
|
id="delete"
|
||||||
onAction={() => setDeleteUser(row)}
|
onAction={() => setDeleteWebsite(id)}
|
||||||
data-test="link-button-delete"
|
data-test="link-button-delete"
|
||||||
>
|
>
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
|
|
@ -79,7 +80,11 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
|
||||||
}}
|
}}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
<Modal isOpen={!!deleteUser}></Modal>
|
<Modal isOpen={!!deleteWebsite}>
|
||||||
|
<Dialog style={{ width: 400 }}>
|
||||||
|
<WebsiteDeleteForm websiteId={deleteWebsite} onClose={() => setDeleteWebsite(null)} />
|
||||||
|
</Dialog>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
<SideMenu items={items} selectedKey={value} />
|
<SideMenu items={items} selectedKey={value} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
<Column>
|
||||||
<Panel>{children}</Panel>
|
<Panel minHeight="300px">{children}</Panel>
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { Users } from '@/components/icons';
|
import { Users } from '@/components/icons';
|
||||||
|
|
@ -15,7 +15,10 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
||||||
const team = useContext(TeamContext);
|
const team = useContext(TeamContext);
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { user } = useLoginQuery();
|
const { user } = useLoginQuery();
|
||||||
const [tab, setTab] = useState('details');
|
const { query, pathname } = useNavigation();
|
||||||
|
const [tab, setTab] = useState(query?.tab || 'details');
|
||||||
|
|
||||||
|
const isAdmin = pathname.includes('/admin');
|
||||||
|
|
||||||
const isTeamOwner =
|
const isTeamOwner =
|
||||||
!!team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) &&
|
!!team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) &&
|
||||||
|
|
@ -32,7 +35,7 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<SectionHeader title={team?.name} icon={<Users />}>
|
<SectionHeader title={team?.name} icon={<Users />}>
|
||||||
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
{!isTeamOwner && !isAdmin && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||||
<TabList>
|
<TabList>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ export function TeamMembersTable({
|
||||||
{allowEdit && (
|
{allowEdit && (
|
||||||
<DataColumn id="action" align="end">
|
<DataColumn id="action" align="end">
|
||||||
{(row: any) => {
|
{(row: any) => {
|
||||||
|
if (row?.role === ROLES.teamOwner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row alignItems="center">
|
<Row alignItems="center">
|
||||||
<TeamMemberEditButton teamId={teamId} userId={row?.user?.id} role={row?.role} />
|
<TeamMemberEditButton teamId={teamId} userId={row?.user?.id} role={row?.role} />
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export function WebsitesTable({
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{allowEdit && (
|
{allowEdit && (
|
||||||
<MenuItem href={renderUrl(`/settings/websites/${websiteId}`)}>
|
<MenuItem href={`/settings/websites/${websiteId}`}>
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
<Icon data-test="link-button-edit">
|
<Icon data-test="link-button-edit">
|
||||||
<SquarePen />
|
<SquarePen />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useApi, useMessages } from '@/components/hooks';
|
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||||
import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm';
|
import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm';
|
||||||
|
|
||||||
const CONFIRM_VALUE = 'DELETE';
|
const CONFIRM_VALUE = 'DELETE';
|
||||||
|
|
@ -17,10 +17,13 @@ export function WebsiteDeleteForm({
|
||||||
const { mutate, isPending, error } = useMutation({
|
const { mutate, isPending, error } = useMutation({
|
||||||
mutationFn: () => del(`/websites/${websiteId}`),
|
mutationFn: () => del(`/websites/${websiteId}`),
|
||||||
});
|
});
|
||||||
|
const { touch } = useModified();
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
mutate(null, {
|
mutate(null, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
|
touch('websites');
|
||||||
|
touch(`websites:${websiteId}`);
|
||||||
onSave?.();
|
onSave?.();
|
||||||
onClose?.();
|
onClose?.();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export function WebsiteShareForm({ websiteId, shareId, onSave, onClose }: Websit
|
||||||
<Row>
|
<Row>
|
||||||
{id && <Button onPress={handleGenerate}>{formatMessage(labels.regenerate)}</Button>}
|
{id && <Button onPress={handleGenerate}>{formatMessage(labels.regenerate)}</Button>}
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row alignItems="center" gap>
|
||||||
{onClose && <Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>}
|
{onClose && <Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>}
|
||||||
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function WebsitesPage() {
|
||||||
<WebsiteAddButton teamId={teamId} />
|
<WebsiteAddButton teamId={teamId} />
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Panel>
|
<Panel>
|
||||||
<WebsitesDataTable teamId={teamId} allowEdit={false} />
|
<WebsitesDataTable teamId={teamId} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</Column>
|
</Column>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,11 @@ import { Share, Edit } from '@/components/icons';
|
||||||
import { Favicon } from '@/components/common/Favicon';
|
import { Favicon } from '@/components/common/Favicon';
|
||||||
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
||||||
import { WebsiteShareForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
import { WebsiteShareForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
|
|
||||||
export function WebsiteHeader() {
|
export function WebsiteHeader() {
|
||||||
const website = useWebsite();
|
const website = useWebsite();
|
||||||
const { renderUrl } = useNavigation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} showBorder={false}>
|
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} showBorder={false}>
|
||||||
|
|
@ -18,7 +17,7 @@ export function WebsiteHeader() {
|
||||||
<ActiveUsers websiteId={website.id} />
|
<ActiveUsers websiteId={website.id} />
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
||||||
<LinkButton href={renderUrl(`/settings/websites/${website.id}`)}>
|
<LinkButton href={`/settings/websites/${website.id}`}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Edit />
|
<Edit />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|
@ -42,7 +41,7 @@ const ShareButton = ({ websiteId, shareId }) => {
|
||||||
<Text>Share</Text>
|
<Text>Share</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog title={formatMessage(labels.share)} style={{ width: 400 }}>
|
<Dialog title={formatMessage(labels.share)} style={{ width: 600 }}>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return <WebsiteShareForm websiteId={websiteId} shareId={shareId} onClose={close} />;
|
return <WebsiteShareForm websiteId={websiteId} shareId={shareId} onClose={close} />;
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters({
|
const filters = await getQueryFilters({
|
||||||
...query,
|
...query,
|
||||||
websiteId,
|
websiteId,
|
||||||
startAt: subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(),
|
startAt: subMinutes(startOfMinute(new Date()), REALTIME_RANGE).getTime(),
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getAttribution(websiteId, parameters as AttributionParameters, filters);
|
const data = await getAttribution(websiteId, parameters as AttributionParameters, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters);
|
const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getFunnel(websiteId, parameters as FunnelParameters, filters);
|
const data = await getFunnel(websiteId, parameters as FunnelParameters, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getGoal(websiteId, parameters as GoalParameters, filters);
|
const data = await getGoal(websiteId, parameters as GoalParameters, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export async function POST(request: Request) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryFilters = await setWebsiteDate(websiteId, getQueryFilters(filters));
|
const queryFilters = await setWebsiteDate(websiteId, await getQueryFilters(filters));
|
||||||
|
|
||||||
const data = await getJourney(websiteId, parameters, queryFilters);
|
const data = await getJourney(websiteId, parameters, queryFilters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export async function POST(request: Request) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
|
|
||||||
const data = await getRetention(websiteId, parameters as RetentionParameters, filters);
|
const data = await getRetention(websiteId, parameters as RetentionParameters, filters);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getRevenue(websiteId, parameters as RevenuParameters, filters);
|
const data = await getRevenue(websiteId, parameters as RevenuParameters, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
const parameters = await setWebsiteDate(websiteId, body.parameters);
|
||||||
const filters = getQueryFilters(body.filters);
|
const filters = await getQueryFilters(body.filters);
|
||||||
|
|
||||||
const data = await getUTM(websiteId, parameters as UTMParameters, filters);
|
const data = await getUTM(websiteId, parameters as UTMParameters, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ team
|
||||||
return unauthorized('You must be the owner of this team.');
|
return unauthorized('You must be the owner of this team.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const users = await getTeamUsers(
|
const users = await getTeamUsers(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ user
|
||||||
}
|
}
|
||||||
|
|
||||||
const { userId } = await params;
|
const { userId } = await params;
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const websites = await getAllUserWebsitesIncludingTeamOwner(userId);
|
const websites = await getAllUserWebsitesIncludingTeamOwner(userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { event } = query;
|
const { event } = query;
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataEvents(websiteId, {
|
const data = await getEventDataEvents(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataFields(websiteId, filters);
|
const data = await getEventDataFields(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { propertyName } = query;
|
const { propertyName } = query;
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataProperties(websiteId, { ...filters, propertyName });
|
const data = await getEventDataProperties(websiteId, { ...filters, propertyName });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataStats(websiteId, filters);
|
const data = await getEventDataStats(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { eventName, propertyName } = query;
|
const { eventName, propertyName } = query;
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getEventDataValues(websiteId, {
|
const data = await getEventDataValues(websiteId, {
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getWebsiteEvents(websiteId, filters);
|
const data = await getWebsiteEvents(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const filters = await setWebsiteDate(websiteId, await getQueryFilters(query));
|
||||||
|
|
||||||
const data = await getEventMetrics(websiteId, filters);
|
const data = await getEventMetrics(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ import {
|
||||||
VIDEO_DOMAINS,
|
VIDEO_DOMAINS,
|
||||||
PAID_AD_PARAMS,
|
PAID_AD_PARAMS,
|
||||||
} from '@/lib/constants';
|
} from '@/lib/constants';
|
||||||
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||||
import { json, unauthorized, badRequest } from '@/lib/response';
|
import { json, unauthorized, badRequest } from '@/lib/response';
|
||||||
import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries';
|
import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries';
|
||||||
import { filterParams } from '@/lib/schema';
|
import { dateRangeParams, filterParams, searchParams } from '@/lib/schema';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -22,11 +22,10 @@ export async function GET(
|
||||||
) {
|
) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
startAt: z.coerce.number().int(),
|
|
||||||
endAt: z.coerce.number().int(),
|
|
||||||
limit: z.coerce.number().optional(),
|
limit: z.coerce.number().optional(),
|
||||||
offset: z.coerce.number().optional(),
|
offset: z.coerce.number().optional(),
|
||||||
search: z.string().optional(),
|
...dateRangeParams,
|
||||||
|
...searchParams,
|
||||||
...filterParams,
|
...filterParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -37,13 +36,13 @@ export async function GET(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const { type, limit, offset, search } = query;
|
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const { type, limit, offset, search } = query;
|
||||||
|
const filters = await getQueryFilters(query, websiteId);
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
filters[type] = `c.${search}`;
|
filters[type] = `c.${search}`;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { getQueryFilters, parseRequest, setWebsiteDate } from '@/lib/request';
|
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||||
import { dateRangeParams, filterParams } from '@/lib/schema';
|
import { dateRangeParams, filterParams } from '@/lib/schema';
|
||||||
import { getCompareDate } from '@/lib/date';
|
import { getCompareDate } from '@/lib/date';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const filters = await getQueryFilters(query, websiteId);
|
||||||
|
|
||||||
const [pageviews, sessions] = await Promise.all([
|
const [pageviews, sessions] = await Promise.all([
|
||||||
getPageviewStats(websiteId, filters),
|
getPageviewStats(websiteId, filters),
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export async function GET(
|
||||||
|
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const { propertyName } = query;
|
const { propertyName } = query;
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export async function GET(
|
||||||
|
|
||||||
const { propertyName } = query;
|
const { propertyName } = query;
|
||||||
const { websiteId } = await params;
|
const { websiteId } = await params;
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const filters = await setWebsiteDate(websiteId, await getQueryFilters(query));
|
||||||
|
|
||||||
if (!(await canViewWebsite(auth, websiteId))) {
|
if (!(await canViewWebsite(auth, websiteId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getSessionActivity(websiteId, sessionId, filters);
|
const data = await getSessionActivity(websiteId, sessionId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const filters = await setWebsiteDate(websiteId, await getQueryFilters(query));
|
||||||
|
|
||||||
const data = await getWebsiteSessions(websiteId, filters);
|
const data = await getWebsiteSessions(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const filters = await setWebsiteDate(websiteId, await getQueryFilters(query));
|
||||||
|
|
||||||
const metrics = await getWebsiteSessionStats(websiteId, filters);
|
const metrics = await getWebsiteSessionStats(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
|
|
||||||
const data = await getWebsiteSessionsWeekly(websiteId, filters);
|
const data = await getWebsiteSessionsWeekly(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { parseRequest, getQueryFilters, setWebsiteDate } from '@/lib/request';
|
import { parseRequest, getQueryFilters } from '@/lib/request';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewWebsite } from '@/lib/auth';
|
import { canViewWebsite } from '@/lib/auth';
|
||||||
import { filterParams } from '@/lib/schema';
|
import { dateRangeParams, filterParams } from '@/lib/schema';
|
||||||
import { getWebsiteStats } from '@/queries';
|
import { getWebsiteStats } from '@/queries';
|
||||||
import { getCompareDate } from '@/lib/date';
|
import { getCompareDate } from '@/lib/date';
|
||||||
|
|
||||||
|
|
@ -11,9 +11,8 @@ export async function GET(
|
||||||
{ params }: { params: Promise<{ websiteId: string }> },
|
{ params }: { params: Promise<{ websiteId: string }> },
|
||||||
) {
|
) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
startAt: z.coerce.number().int(),
|
|
||||||
endAt: z.coerce.number().int(),
|
|
||||||
compare: z.string().optional(),
|
compare: z.string().optional(),
|
||||||
|
...dateRangeParams,
|
||||||
...filterParams,
|
...filterParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -29,7 +28,7 @@ export async function GET(
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await setWebsiteDate(websiteId, getQueryFilters(query));
|
const filters = await getQueryFilters(query, websiteId);
|
||||||
|
|
||||||
const data = await getWebsiteStats(websiteId, filters);
|
const data = await getWebsiteStats(websiteId, filters);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export async function GET(
|
||||||
if (FILTER_GROUPS[type]) {
|
if (FILTER_GROUPS[type]) {
|
||||||
values = (await getWebsiteSegments(websiteId, type)).map(segment => ({ value: segment.name }));
|
values = (await getWebsiteSegments(websiteId, type)).map(segment => ({ value: segment.name }));
|
||||||
} else {
|
} else {
|
||||||
const filters = getQueryFilters(query);
|
const filters = await getQueryFilters(query);
|
||||||
values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
|
values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,16 +42,18 @@ export function Pager({ page, pageSize, count, onPageChange }: PagerProps) {
|
||||||
total: maxPage.toLocaleString(),
|
total: maxPage.toLocaleString(),
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<Button onPress={() => handlePageChange(-1)} isDisabled={firstPage}>
|
<Row gap="1">
|
||||||
<Icon size="sm" rotate={180}>
|
<Button variant="outline" onPress={() => handlePageChange(-1)} isDisabled={firstPage}>
|
||||||
<Chevron />
|
<Icon size="sm" rotate={180}>
|
||||||
</Icon>
|
<Chevron />
|
||||||
</Button>
|
</Icon>
|
||||||
<Button onPress={() => handlePageChange(1)} isDisabled={lastPage}>
|
</Button>
|
||||||
<Icon size="sm">
|
<Button variant="outline" onPress={() => handlePageChange(1)} isDisabled={lastPage}>
|
||||||
<Chevron />
|
<Icon size="sm">
|
||||||
</Icon>
|
<Chevron />
|
||||||
</Button>
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ export function useTeamsQuery(params?: Record<string, any>, options?: ReactQuery
|
||||||
queryKey: ['teams:admin', { modified, ...params }],
|
queryKey: ['teams:admin', { modified, ...params }],
|
||||||
queryFn: pageParams => {
|
queryFn: pageParams => {
|
||||||
return get(`/admin/teams`, {
|
return get(`/admin/teams`, {
|
||||||
...params,
|
|
||||||
...pageParams,
|
...pageParams,
|
||||||
|
...params,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ export function useUserWebsitesQuery(
|
||||||
queryKey: ['websites', { userId, teamId, modified, ...params }],
|
queryKey: ['websites', { userId, teamId, modified, ...params }],
|
||||||
queryFn: pageParams => {
|
queryFn: pageParams => {
|
||||||
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
|
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
|
||||||
...params,
|
|
||||||
...pageParams,
|
...pageParams,
|
||||||
|
...params,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,24 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Select, SelectProps, ListItem } from '@umami/react-zen';
|
import { Select, SelectProps, ListItem } from '@umami/react-zen';
|
||||||
import { useUserWebsitesQuery, useMessages } from '@/components/hooks';
|
import { useUserWebsitesQuery, useWebsiteQuery, useNavigation } from '@/components/hooks';
|
||||||
|
|
||||||
export function WebsiteSelect({
|
export function WebsiteSelect({
|
||||||
websiteId,
|
websiteId,
|
||||||
teamId,
|
teamId,
|
||||||
variant,
|
variant,
|
||||||
onSelect,
|
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
websiteId?: string;
|
websiteId?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero';
|
variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero';
|
||||||
onSelect?: (key: any) => void;
|
|
||||||
} & SelectProps) {
|
} & SelectProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { router, renderUrl } = useNavigation();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [selectedId, setSelectedId] = useState(websiteId);
|
const { data: website } = useWebsiteQuery(websiteId);
|
||||||
|
|
||||||
const { data, isLoading } = useUserWebsitesQuery({ teamId }, { search, pageSize: 5 });
|
const { data, isLoading } = useUserWebsitesQuery({ teamId }, { search, pageSize: 5 });
|
||||||
|
|
||||||
const handleSelect = (value: any) => {
|
const handleSelect = (value: any) => {
|
||||||
setSelectedId(value);
|
router.push(renderUrl(`/websites/${value}`));
|
||||||
onSelect?.(value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (value: string) => {
|
const handleSearch = (value: string) => {
|
||||||
|
|
@ -33,14 +29,14 @@ export function WebsiteSelect({
|
||||||
<Select
|
<Select
|
||||||
{...props}
|
{...props}
|
||||||
items={data?.['data'] || []}
|
items={data?.['data'] || []}
|
||||||
value={selectedId}
|
value={websiteId}
|
||||||
placeholder={formatMessage(labels.selectWebsite)}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
buttonProps={{ variant }}
|
buttonProps={{ variant }}
|
||||||
allowSearch={true}
|
allowSearch={true}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
|
renderValue={() => website?.name}
|
||||||
>
|
>
|
||||||
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
|
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export function getRequestDateRange(query: Record<string, string>) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRequestFilters(query: Record<string, any>, websiteId?: string) {
|
export function getRequestFilters(query: Record<string, any>) {
|
||||||
const result: Record<string, any> = {};
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
for (const key of Object.keys(FILTER_COLUMNS)) {
|
for (const key of Object.keys(FILTER_COLUMNS)) {
|
||||||
|
|
@ -76,20 +76,17 @@ export async function getRequestFilters(query: Record<string, any>, websiteId?:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRequestSegments(websiteId: string, query: Record<string, any>) {
|
||||||
for (const key of Object.keys(FILTER_GROUPS)) {
|
for (const key of Object.keys(FILTER_GROUPS)) {
|
||||||
const value = query[key];
|
const value = query[key];
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
const segment = await getWebsiteSegment(websiteId, key, value);
|
return getWebsiteSegment(websiteId, key, value);
|
||||||
if (key === 'segment') {
|
|
||||||
// merge filters into result
|
|
||||||
Object.assign(result, segment.parameters);
|
|
||||||
} else {
|
|
||||||
result[key] = segment.parameters;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
|
export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
|
||||||
|
|
@ -102,13 +99,18 @@ export async function setWebsiteDate(websiteId: string, data: Record<string, any
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getQueryFilters(params: Record<string, any>): QueryFilters {
|
export async function getQueryFilters(
|
||||||
const dateRange = getRequestDateRange(params);
|
params: Record<string, any>,
|
||||||
|
websiteId?: string,
|
||||||
|
): Promise<QueryFilters> {
|
||||||
|
const dateRange = await setWebsiteDate(websiteId, getRequestDateRange(params));
|
||||||
const filters = getRequestFilters(params);
|
const filters = getRequestFilters(params);
|
||||||
|
const segments = await getRequestSegments(websiteId, params);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...dateRange,
|
...dateRange,
|
||||||
...filters,
|
...filters,
|
||||||
|
...segments,
|
||||||
page: params?.page,
|
page: params?.page,
|
||||||
pageSize: params?.page ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
|
pageSize: params?.page ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
|
||||||
orderBy: params?.orderBy,
|
orderBy: params?.orderBy,
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,9 @@ export interface FilterParams {
|
||||||
search?: string;
|
search?: string;
|
||||||
tag?: string;
|
tag?: string;
|
||||||
eventType?: number;
|
eventType?: number;
|
||||||
|
segment?: string;
|
||||||
|
cohort?: string;
|
||||||
|
compare?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SortParams {
|
export interface SortParams {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ async function clickhouseQuery(
|
||||||
from (
|
from (
|
||||||
select arrayJoin(event_name) as event_name,
|
select arrayJoin(event_name) as event_name,
|
||||||
created_at
|
created_at
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly as website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ async function clickhouseQuery(
|
||||||
select
|
select
|
||||||
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
||||||
sum(views) as y
|
sum(views) as y
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly as website_event
|
||||||
${cohortQuery}
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ async function clickhouseQuery(
|
||||||
${column} x,
|
${column} x,
|
||||||
uniq(session_id) y
|
uniq(session_id) y
|
||||||
${includeCountry ? ', country' : ''}
|
${includeCountry ? ', country' : ''}
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly as website_event
|
||||||
${cohortQuery}
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ async function clickhouseQuery(
|
||||||
select
|
select
|
||||||
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
${getDateSQL('website_event.created_at', unit, timezone)} as t,
|
||||||
uniq(session_id) as y
|
uniq(session_id) as y
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly as website_event
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
|
||||||
and event_type = {eventType:UInt32}
|
and event_type = {eventType:UInt32}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
|
||||||
uniq(visit_id) as visits,
|
uniq(visit_id) as visits,
|
||||||
sumIf(views, event_type = 1) as views,
|
sumIf(views, event_type = 1) as views,
|
||||||
lastAt as createdAt
|
lastAt as createdAt
|
||||||
from website_event_stats_hourly website_event
|
from website_event_stats_hourly as website_event
|
||||||
${cohortQuery}
|
${cohortQuery}
|
||||||
where website_id = {websiteId:UUID}
|
where website_id = {websiteId:UUID}
|
||||||
${dateQuery}
|
${dateQuery}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue