diff --git a/prisma/migrations/15_add_share/migration.sql b/prisma/migrations/15_add_share/migration.sql index 3971b54c..89aece1e 100644 --- a/prisma/migrations/15_add_share/migration.sql +++ b/prisma/migrations/15_add_share/migration.sql @@ -2,8 +2,8 @@ CREATE TABLE "share" ( "share_id" UUID NOT NULL, "entity_id" UUID NOT NULL, - "share_type" INTEGER NOT NULL, "name" VARCHAR(200) NOT NULL, + "share_type" INTEGER NOT NULL, "slug" VARCHAR(100) NOT NULL, "parameters" JSONB NOT NULL, "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, @@ -12,9 +12,6 @@ CREATE TABLE "share" ( CONSTRAINT "share_pkey" PRIMARY KEY ("share_id") ); --- CreateIndex -CREATE UNIQUE INDEX "share_share_id_key" ON "share"("share_id"); - -- CreateIndex CREATE UNIQUE INDEX "share_slug_key" ON "share"("slug"); @@ -28,7 +25,7 @@ SELECT gen_random_uuid(), name, 1, share_id, - '{}'::jsonb, + '{"overview":true}'::jsonb, now() FROM "website" WHERE share_id IS NOT NULL; diff --git a/prisma/migrations/16_boards/migration.sql b/prisma/migrations/16_boards/migration.sql index ef0b28dc..ad8ee172 100644 --- a/prisma/migrations/16_boards/migration.sql +++ b/prisma/migrations/16_boards/migration.sql @@ -14,9 +14,6 @@ CREATE TABLE "board" ( CONSTRAINT "board_pkey" PRIMARY KEY ("board_id") ); --- CreateIndex -CREATE UNIQUE INDEX "board_board_id_key" ON "board"("board_id"); - -- CreateIndex CREATE UNIQUE INDEX "board_slug_key" ON "board"("slug"); diff --git a/prisma/migrations/17_remove_duplicate_key/migration.sql b/prisma/migrations/17_remove_duplicate_key/migration.sql index a49eed8f..75f7191e 100644 --- a/prisma/migrations/17_remove_duplicate_key/migration.sql +++ b/prisma/migrations/17_remove_duplicate_key/migration.sql @@ -1,6 +1,3 @@ --- DropIndex -DROP INDEX "board_board_id_key"; - -- DropIndex DROP INDEX "link_link_id_key"; @@ -19,9 +16,6 @@ DROP INDEX "segment_segment_id_key"; -- DropIndex DROP INDEX "session_session_id_key"; --- DropIndex -DROP INDEX "share_share_id_key"; - -- DropIndex DROP INDEX "team_team_id_key"; diff --git a/src/app/(main)/admin/teams/AdminTeamsPage.tsx b/src/app/(main)/admin/teams/AdminTeamsPage.tsx index 41e6f4af..7905f7f6 100644 --- a/src/app/(main)/admin/teams/AdminTeamsPage.tsx +++ b/src/app/(main)/admin/teams/AdminTeamsPage.tsx @@ -3,14 +3,19 @@ import { Column } from '@umami/react-zen'; import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; import { useMessages } from '@/components/hooks'; +import { TeamsAddButton } from '../../teams/TeamsAddButton'; import { AdminTeamsDataTable } from './AdminTeamsDataTable'; export function AdminTeamsPage() { const { formatMessage, labels } = useMessages(); + const handleSave = () => {}; + return ( - + + + diff --git a/src/app/(main)/teams/TeamAddForm.tsx b/src/app/(main)/teams/TeamAddForm.tsx index c95259f4..3b827776 100644 --- a/src/app/(main)/teams/TeamAddForm.tsx +++ b/src/app/(main)/teams/TeamAddForm.tsx @@ -7,8 +7,17 @@ import { TextField, } from '@umami/react-zen'; import { useMessages, useUpdateQuery } from '@/components/hooks'; +import { UserSelect } from '@/components/input/UserSelect'; -export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { +export function TeamAddForm({ + onSave, + onClose, + isAdmin, +}: { + onSave: () => void; + onClose: () => void; + isAdmin: boolean; +}) { const { formatMessage, labels, getErrorMessage } = useMessages(); const { mutateAsync, error, isPending } = useUpdateQuery('/teams'); @@ -26,6 +35,11 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: + {isAdmin && ( + + + + )} + + {formatMessage(labels.save)} + + + + ); +} diff --git a/src/app/(main)/teams/TeamsAddButton.tsx b/src/app/(main)/teams/TeamsAddButton.tsx index 578a273a..13873088 100644 --- a/src/app/(main)/teams/TeamsAddButton.tsx +++ b/src/app/(main)/teams/TeamsAddButton.tsx @@ -4,7 +4,13 @@ import { Plus } from '@/components/icons'; import { messages } from '@/components/messages'; import { TeamAddForm } from './TeamAddForm'; -export function TeamsAddButton({ onSave }: { onSave?: () => void }) { +export function TeamsAddButton({ + onSave, + isAdmin = false, +}: { + onSave?: () => void; + isAdmin?: boolean; +}) { const { formatMessage, labels } = useMessages(); const { toast } = useToast(); const { touch } = useModified(); @@ -25,7 +31,7 @@ export function TeamsAddButton({ onSave }: { onSave?: () => void }) { - {({ close }) => } + {({ close }) => } diff --git a/src/app/(main)/teams/TeamsMemberAddButton.tsx b/src/app/(main)/teams/TeamsMemberAddButton.tsx new file mode 100644 index 00000000..f1bbf258 --- /dev/null +++ b/src/app/(main)/teams/TeamsMemberAddButton.tsx @@ -0,0 +1,40 @@ +import { Button, Dialog, DialogTrigger, Icon, Modal, Text, useToast } from '@umami/react-zen'; +import { useMessages, useModified } from '@/components/hooks'; +import { Plus } from '@/components/icons'; +import { messages } from '@/components/messages'; +import { TeamMemberAddForm } from './TeamMemberAddForm'; + +export function TeamsMemberAddButton({ + teamId, + onSave, +}: { + teamId: string; + onSave?: () => void; + isAdmin?: boolean; +}) { + const { formatMessage, labels } = useMessages(); + const { toast } = useToast(); + const { touch } = useModified(); + + const handleSave = async () => { + toast(formatMessage(messages.saved)); + touch('teams:members'); + onSave?.(); + }; + + return ( + + + + + {({ close }) => } + + + + ); +} diff --git a/src/app/(main)/teams/[teamId]/TeamSettings.tsx b/src/app/(main)/teams/[teamId]/TeamSettings.tsx index 3ddbe000..4bbb8905 100644 --- a/src/app/(main)/teams/[teamId]/TeamSettings.tsx +++ b/src/app/(main)/teams/[teamId]/TeamSettings.tsx @@ -1,10 +1,12 @@ -import { Column } from '@umami/react-zen'; +import { Column, Heading, Row } from '@umami/react-zen'; import { TeamLeaveButton } from '@/app/(main)/teams/TeamLeaveButton'; import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; -import { useLoginQuery, useNavigation, useTeam } from '@/components/hooks'; +import { useLoginQuery, useMessages, useNavigation, useTeam } from '@/components/hooks'; import { Users } from '@/components/icons'; +import { labels } from '@/components/messages'; import { ROLES } from '@/lib/constants'; +import { TeamsMemberAddButton } from '../TeamsMemberAddButton'; import { TeamEditForm } from './TeamEditForm'; import { TeamManage } from './TeamManage'; import { TeamMembersDataTable } from './TeamMembersDataTable'; @@ -13,6 +15,7 @@ export function TeamSettings({ teamId }: { teamId: string }) { const team: any = useTeam(); const { user } = useLoginQuery(); const { pathname } = useNavigation(); + const { formatMessage } = useMessages(); const isAdmin = pathname.includes('/admin'); @@ -37,6 +40,10 @@ export function TeamSettings({ teamId }: { teamId: string }) { + + {formatMessage(labels.members)} + {isAdmin && } + {isTeamOwner && ( diff --git a/src/app/(main)/websites/[websiteId]/(reports)/journeys/Journey.tsx b/src/app/(main)/websites/[websiteId]/(reports)/journeys/Journey.tsx index 3327a425..1b893d27 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/journeys/Journey.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/journeys/Journey.tsx @@ -21,9 +21,15 @@ export interface JourneyProps { steps: number; startStep?: string; endStep?: string; + view: string; } -export function Journey({ websiteId, steps, startStep, endStep }: JourneyProps) { +const EVENT_TYPES = { + views: 1, + events: 2, +}; + +export function Journey({ websiteId, steps, startStep, endStep, view }: JourneyProps) { const [selectedNode, setSelectedNode] = useState(null); const [activeNode, setActiveNode] = useState(null); const { formatMessage, labels } = useMessages(); @@ -32,6 +38,8 @@ export function Journey({ websiteId, steps, startStep, endStep }: JourneyProps) steps, startStep, endStep, + view, + eventType: EVENT_TYPES[view], }); useEscapeKey(() => setSelectedNode(null)); diff --git a/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx b/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx index 14b8341d..c2dd8349 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage.tsx @@ -1,5 +1,6 @@ 'use client'; -import { Column, Grid, ListItem, SearchField, Select } from '@umami/react-zen'; +import { Column, Grid, ListItem, Row, SearchField, Select } from '@umami/react-zen'; +import { FilterButtons } from 'dist'; import { useState } from 'react'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { Panel } from '@/components/common/Panel'; @@ -14,10 +15,26 @@ export function JourneysPage({ websiteId }: { websiteId: string }) { const { dateRange: { startDate, endDate }, } = useDateRange(); + const [view, setView] = useState('all'); const [steps, setSteps] = useState(DEFAULT_STEP); const [startStep, setStartStep] = useState(''); const [endStep, setEndStep] = useState(''); + const buttons = [ + { + id: 'all', + label: formatMessage(labels.all), + }, + { + id: 'views', + label: formatMessage(labels.views), + }, + { + id: 'events', + label: formatMessage(labels.events), + }, + ]; + return ( @@ -52,6 +69,9 @@ export function JourneysPage({ websiteId }: { websiteId: string }) { /> + + + diff --git a/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx b/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx index b2ea2a83..896c733a 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx @@ -21,30 +21,28 @@ export function WebsiteChart({ const { pageviews, sessions, compare } = (data || {}) as any; const chartData = useMemo(() => { - if (data) { - const result = { - pageviews, - sessions, - }; + if (!data) { + return { pageviews: [], sessions: [] }; + } - if (compare) { - result.compare = { - pageviews: result.pageviews.map(({ x }, i) => ({ + return { + pageviews, + sessions, + ...(compare && { + compare: { + pageviews: pageviews.map(({ x }, i) => ({ x, y: compare.pageviews[i]?.y, d: compare.pageviews[i]?.x, })), - sessions: result.sessions.map(({ x }, i) => ({ + sessions: sessions.map(({ x }, i) => ({ x, y: compare.sessions[i]?.y, d: compare.sessions[i]?.x, })), - }; - } - - return result; - } - return { pageviews: [], sessions: [] }; + }, + }), + }; }, [data, startDate, endDate, unit]); return ( diff --git a/src/app/(main)/websites/[websiteId]/WebsiteExpandedMenu.tsx b/src/app/(main)/websites/[websiteId]/WebsiteExpandedMenu.tsx index 29c3954f..4bac4ff6 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteExpandedMenu.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteExpandedMenu.tsx @@ -169,6 +169,12 @@ export function WebsiteExpandedMenu({ path: updateParams({ view: 'hostname' }), icon: , }, + { + id: 'distinctId', + label: formatMessage(labels.distinctId), + path: updateParams({ view: 'distinctId' }), + icon: , + }, { id: 'tag', label: formatMessage(labels.tag), diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx index 30189534..132d3b14 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx @@ -10,7 +10,7 @@ import { } from '@umami/react-zen'; import { Fragment } from 'react'; import { useMessages, useNavigation } from '@/components/hooks'; -import { Edit, More, Share } from '@/components/icons'; +import { Edit, MoreHorizontal, Share } from '@/components/icons'; export function WebsiteMenu({ websiteId }: { websiteId: string }) { const { formatMessage, labels } = useMessages(); @@ -33,7 +33,7 @@ export function WebsiteMenu({ websiteId }: { websiteId: string }) { diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx index 6c91ba6d..605ee385 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx @@ -7,14 +7,18 @@ import { formatLongNumber, formatShortTime } from '@/lib/format'; export function WebsiteMetricsBar({ websiteId, + compareMode, }: { websiteId: string; showChange?: boolean; compareMode?: boolean; }) { - const { isAllTime } = useDateRange(); + const { isAllTime, dateCompare } = useDateRange(); const { formatMessage, labels, getErrorMessage } = useMessages(); - const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId); + const { data, isLoading, isFetching, error } = useWebsiteStatsQuery({ + websiteId, + compare: compareMode ? dateCompare?.compare : undefined, + }); const { pageviews, visitors, visits, bounces, totaltime, comparison } = data || {}; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx index ad05b706..9f72c303 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx @@ -29,6 +29,7 @@ export function WebsiteNav({ event: undefined, compare: undefined, view: undefined, + unit: undefined, }); const items = [ diff --git a/src/app/(main)/websites/[websiteId]/WebsitePage.tsx b/src/app/(main)/websites/[websiteId]/WebsitePage.tsx index f587e112..5acc9e68 100644 --- a/src/app/(main)/websites/[websiteId]/WebsitePage.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsitePage.tsx @@ -1,7 +1,8 @@ 'use client'; -import { Column } from '@umami/react-zen'; +import { Column, Row } from '@umami/react-zen'; import { ExpandedViewModal } from '@/app/(main)/websites/[websiteId]/ExpandedViewModal'; import { Panel } from '@/components/common/Panel'; +import { UnitFilter } from '@/components/input/UnitFilter'; import { WebsiteChart } from './WebsiteChart'; import { WebsiteControls } from './WebsiteControls'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; @@ -13,6 +14,9 @@ export function WebsitePage({ websiteId }: { websiteId: string }) { + + + diff --git a/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx b/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx index bca8d244..32d641b0 100644 --- a/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx +++ b/src/app/(main)/websites/[websiteId]/compare/ComparePage.tsx @@ -10,7 +10,7 @@ export function ComparePage({ websiteId }: { websiteId: string }) { return ( - + diff --git a/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx b/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx index 13c05160..4daf17fc 100644 --- a/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx +++ b/src/app/(main)/websites/[websiteId]/compare/CompareTables.tsx @@ -93,6 +93,11 @@ export function CompareTables({ websiteId }: { websiteId: string }) { label: formatMessage(labels.hostname), path: renderPath('hostname'), }, + { + id: 'distinctId', + label: formatMessage(labels.distinctId), + path: renderPath('distinctId'), + }, { id: 'tag', label: formatMessage(labels.tags), diff --git a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx index 7fb2eb41..41c2b1e8 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx @@ -25,6 +25,19 @@ export function EventsTable(props: DataTableProps) { const { updateParams } = useNavigation(); const { formatValue } = useFormat(); + const renderLink = (label: string, hostname: string) => { + return ( + + {label} + + ); + }; + return ( @@ -43,7 +56,7 @@ export function EventsTable(props: DataTableProps) { title={row.eventName || row.urlPath} truncate > - {row.eventName || row.urlPath} + {row.eventName || renderLink(row.urlPath, row.hostname)} {row.hasData > 0 && } diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx index 10763618..9cbbd371 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx @@ -74,8 +74,9 @@ export function RealtimeLog({ data }: { data: any }) { os: string; country: string; device: string; + hostname: string; }) => { - const { __type, eventName, urlPath, browser, os, country, device } = log; + const { __type, eventName, urlPath, browser, os, country, device, hostname } = log; if (__type === TYPE_EVENT) { return ( @@ -86,7 +87,8 @@ export function RealtimeLog({ data }: { data: any }) { url: ( @@ -100,7 +102,12 @@ export function RealtimeLog({ data }: { data: any }) { if (__type === TYPE_PAGEVIEW) { return ( - + {urlPath} ); diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx index cbb28108..df0ef834 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionActivity.tsx @@ -39,10 +39,23 @@ export function SessionActivity({ const { isMobile } = useMobile(); let lastDay = null; + const renderLink = (label: string, hostname: string) => { + return ( + + {label} + + ); + }; + return ( - {data?.map(({ eventId, createdAt, urlPath, eventName, visitId, hasData }) => { + {data?.map(({ eventId, createdAt, urlPath, eventName, visitId, hostname, hasData }) => { const showHeader = !lastDay || !isSameDay(new Date(lastDay), new Date(createdAt)); lastDay = createdAt; @@ -61,7 +74,7 @@ export function SessionActivity({ : formatMessage(labels.viewedPage)} - {eventName || urlPath} + {eventName || renderLink(urlPath, hostname)} {hasData > 0 && } diff --git a/src/app/api/reports/journey/route.ts b/src/app/api/reports/journey/route.ts index 29e85319..b53d225d 100644 --- a/src/app/api/reports/journey/route.ts +++ b/src/app/api/reports/journey/route.ts @@ -12,11 +12,16 @@ export async function POST(request: Request) { } const { websiteId, parameters, filters } = body; + const { eventType } = parameters; if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } + if (eventType) { + filters.eventType = eventType; + } + const queryFilters = await getQueryFilters(filters, websiteId); const data = await getJourney(websiteId, parameters, queryFilters); diff --git a/src/app/api/teams/route.ts b/src/app/api/teams/route.ts index 53ef5923..c571f405 100644 --- a/src/app/api/teams/route.ts +++ b/src/app/api/teams/route.ts @@ -28,6 +28,7 @@ export async function GET(request: Request) { export async function POST(request: Request) { const schema = z.object({ name: z.string().max(50), + ownerId: z.uuid().optional(), }); const { auth, body, error } = await parseRequest(request, schema); @@ -40,7 +41,7 @@ export async function POST(request: Request) { return unauthorized(); } - const { name } = body; + const { name, ownerId } = body; const team = await createTeam( { @@ -48,7 +49,7 @@ export async function POST(request: Request) { name, accessCode: `team_${getRandomChars(16)}`, }, - auth.user.id, + ownerId ?? auth.user.id, ); return json(team); diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index b7177b5d..9d21f4f5 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -31,9 +31,11 @@ export async function GET( const data = await getWebsiteStats(websiteId, filters); - const compare = filters.compare ?? 'prev'; - - const { startDate, endDate } = getCompareDate(compare, filters.startDate, filters.endDate); + const { startDate, endDate } = getCompareDate( + filters.compare ?? 'prev', + filters.startDate, + filters.endDate, + ); const comparison = await getWebsiteStats(websiteId, { ...filters, diff --git a/src/components/hooks/queries/useWebsiteExpandedMetricsQuery.ts b/src/components/hooks/queries/useWebsiteExpandedMetricsQuery.ts index b2e90199..1611c7f8 100644 --- a/src/components/hooks/queries/useWebsiteExpandedMetricsQuery.ts +++ b/src/components/hooks/queries/useWebsiteExpandedMetricsQuery.ts @@ -19,7 +19,7 @@ export function useWebsiteExpandedMetricsQuery( options?: ReactQueryOptions, ) { const { get, useQuery } = useApi(); - const { startAt, endAt, unit, timezone } = useDateParameters(); + const { startAt, endAt } = useDateParameters(); const filters = useFilterParameters(); return useQuery({ @@ -29,8 +29,6 @@ export function useWebsiteExpandedMetricsQuery( websiteId, startAt, endAt, - unit, - timezone, ...filters, ...params, }, @@ -39,8 +37,6 @@ export function useWebsiteExpandedMetricsQuery( get(`/websites/${websiteId}/metrics/expanded`, { startAt, endAt, - unit, - timezone, ...filters, ...params, }), diff --git a/src/components/hooks/queries/useWebsiteMetricsQuery.ts b/src/components/hooks/queries/useWebsiteMetricsQuery.ts index 67c5e4d4..cd064af6 100644 --- a/src/components/hooks/queries/useWebsiteMetricsQuery.ts +++ b/src/components/hooks/queries/useWebsiteMetricsQuery.ts @@ -15,7 +15,7 @@ export function useWebsiteMetricsQuery( options?: ReactQueryOptions, ) { const { get, useQuery } = useApi(); - const { startAt, endAt, unit, timezone } = useDateParameters(); + const { startAt, endAt } = useDateParameters(); const filters = useFilterParameters(); return useQuery({ @@ -25,8 +25,6 @@ export function useWebsiteMetricsQuery( websiteId, startAt, endAt, - unit, - timezone, ...filters, ...params, }, @@ -35,8 +33,6 @@ export function useWebsiteMetricsQuery( get(`/websites/${websiteId}/metrics`, { startAt, endAt, - unit, - timezone, ...filters, ...params, }), diff --git a/src/components/hooks/queries/useWebsiteStatsQuery.ts b/src/components/hooks/queries/useWebsiteStatsQuery.ts index 69bae09f..48484a07 100644 --- a/src/components/hooks/queries/useWebsiteStatsQuery.ts +++ b/src/components/hooks/queries/useWebsiteStatsQuery.ts @@ -1,6 +1,5 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useDateParameters } from '@/components/hooks/useDateParameters'; -import { useDateRange } from '@/components/hooks/useDateRange'; import { useApi } from '../useApi'; import { useFilterParameters } from '../useFilterParameters'; @@ -20,21 +19,16 @@ export interface WebsiteStatsData { } export function useWebsiteStatsQuery( - websiteId: string, + { websiteId, compare }: { websiteId: string; compare?: string }, options?: UseQueryOptions, ) { const { get, useQuery } = useApi(); - const { startAt, endAt, unit, timezone } = useDateParameters(); - const { compare } = useDateRange(); + const { startAt, endAt } = useDateParameters(); const filters = useFilterParameters(); return useQuery({ - queryKey: [ - 'websites:stats', - { websiteId, startAt, endAt, unit, timezone, compare, ...filters }, - ], - queryFn: () => - get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, compare, ...filters }), + queryKey: ['websites:stats', { websiteId, compare, startAt, endAt, ...filters }], + queryFn: () => get(`/websites/${websiteId}/stats`, { compare, startAt, endAt, ...filters }), enabled: !!websiteId, ...options, }); diff --git a/src/components/hooks/queries/useWeeklyTrafficQuery.ts b/src/components/hooks/queries/useWeeklyTrafficQuery.ts index a76ebb3d..df729ffd 100644 --- a/src/components/hooks/queries/useWeeklyTrafficQuery.ts +++ b/src/components/hooks/queries/useWeeklyTrafficQuery.ts @@ -12,13 +12,12 @@ export function useWeeklyTrafficQuery(websiteId: string, params?: Record { return get(`/websites/${websiteId}/sessions/weekly`, { startAt, endAt, - unit, timezone, ...params, ...filters, diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index 755f36ee..5090bd3d 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -7,13 +7,13 @@ import { getItem } from '@/lib/storage'; export function useDateRange(options: { ignoreOffset?: boolean; timezone?: string } = {}) { const { - query: { date = '', offset = 0, compare = 'prev' }, + query: { date = '', unit = '', offset = 0, compare = 'prev' }, } = useNavigation(); const { locale } = useLocale(); - const dateRange = useMemo(() => { const dateRangeObject = parseDateRange( date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE, + unit, locale, options.timezone, ); @@ -21,12 +21,13 @@ export function useDateRange(options: { ignoreOffset?: boolean; timezone?: strin return !options.ignoreOffset && offset ? getOffsetDateRange(dateRangeObject, +offset) : dateRangeObject; - }, [date, offset, options]); + }, [date, unit, offset, options]); const dateCompare = getCompareDate(compare, dateRange.startDate, dateRange.endDate); return { date, + unit, offset, compare, isAllTime: date.endsWith(`:all`), diff --git a/src/components/hooks/useFields.ts b/src/components/hooks/useFields.ts index 22a1dcf3..039b7157 100644 --- a/src/components/hooks/useFields.ts +++ b/src/components/hooks/useFields.ts @@ -15,6 +15,7 @@ export function useFields() { { name: 'region', type: 'string', label: formatMessage(labels.region) }, { name: 'city', type: 'string', label: formatMessage(labels.city) }, { name: 'hostname', type: 'string', label: formatMessage(labels.hostname) }, + { name: 'distinctId', type: 'string', label: formatMessage(labels.distinctId) }, { name: 'tag', type: 'string', label: formatMessage(labels.tag) }, { name: 'event', type: 'string', label: formatMessage(labels.event) }, ]; diff --git a/src/components/hooks/useFilterParameters.ts b/src/components/hooks/useFilterParameters.ts index 54032120..c141a3be 100644 --- a/src/components/hooks/useFilterParameters.ts +++ b/src/components/hooks/useFilterParameters.ts @@ -18,6 +18,7 @@ export function useFilterParameters() { event, tag, hostname, + distinctId, page, pageSize, search, @@ -42,6 +43,7 @@ export function useFilterParameters() { event, tag, hostname, + distinctId, search, segment, cohort, @@ -61,6 +63,7 @@ export function useFilterParameters() { event, tag, hostname, + distinctId, page, pageSize, search, diff --git a/src/components/input/FilterEditForm.tsx b/src/components/input/FilterEditForm.tsx index 44f43844..9221e3a2 100644 --- a/src/components/input/FilterEditForm.tsx +++ b/src/components/input/FilterEditForm.tsx @@ -61,7 +61,9 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP websiteId={websiteId} value={currentFilters} onChange={setCurrentFilters} - exclude={excludeFilters ? ['path', 'title', 'hostname', 'tag', 'event'] : []} + exclude={ + excludeFilters ? ['path', 'title', 'hostname', 'distinctId', 'tag', 'event'] : [] + } /> diff --git a/src/components/input/UnitFilter.tsx b/src/components/input/UnitFilter.tsx new file mode 100644 index 00000000..84a15f35 --- /dev/null +++ b/src/components/input/UnitFilter.tsx @@ -0,0 +1,71 @@ +import { ListItem, Row, Select } from '@umami/react-zen'; +import { useMessages, useNavigation } from '@/components/hooks'; +import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants'; +import { getItem } from '@/lib/storage'; + +export function UnitFilter() { + const { formatMessage, labels } = useMessages(); + const { router, query, updateParams } = useNavigation(); + + const DATE_RANGE_UNIT_CONFIG = { + '0week': { + defaultUnit: 'day', + availableUnits: ['day', 'hour'], + }, + '7day': { + defaultUnit: 'day', + availableUnits: ['day', 'hour'], + }, + '0month': { + defaultUnit: 'day', + availableUnits: ['day', 'hour'], + }, + '30day': { + defaultUnit: 'day', + availableUnits: ['day', 'hour'], + }, + '90day': { + defaultUnit: 'day', + availableUnits: ['day', 'month'], + }, + '6month': { + defaultUnit: 'month', + availableUnits: ['month', 'day'], + }, + }; + + const unitConfig = + DATE_RANGE_UNIT_CONFIG[query.date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE]; + + if (!unitConfig) { + return null; + } + + const handleChange = (value: string) => { + router.push(updateParams({ unit: value })); + }; + + const options = unitConfig.availableUnits.map(unit => ({ + id: unit, + label: formatMessage(labels[unit]), + })); + + const selectedUnit = query.unit ?? unitConfig.defaultUnit; + + return ( + + + + ); +} diff --git a/src/components/input/UserSelect.tsx b/src/components/input/UserSelect.tsx new file mode 100644 index 00000000..ccb3d432 --- /dev/null +++ b/src/components/input/UserSelect.tsx @@ -0,0 +1,71 @@ +import { ListItem, Row, Select, type SelectProps, Text } from '@umami/react-zen'; +import { useMemo, useState } from 'react'; +import { Empty } from '@/components/common/Empty'; +import { useMessages, useTeamMembersQuery, useUsersQuery } from '@/components/hooks'; + +export function UserSelect({ + teamId, + onChange, + ...props +}: { + teamId?: string; +} & SelectProps) { + const { formatMessage, messages } = useMessages(); + const { data: users, isLoading: usersLoading } = useUsersQuery(); + const { data: teamMembers, isLoading: teamMembersLoading } = useTeamMembersQuery(teamId); + const [username, setUsername] = useState(); + const [search, setSearch] = useState(''); + + const listItems = useMemo(() => { + if (!users) { + return []; + } + if (!teamId || !teamMembers) { + return users.data; + } + const teamMemberIds = teamMembers.data.map(({ userId }) => userId); + return users.data.filter(({ id }) => !teamMemberIds.includes(id)); + }, [users, teamMembers, teamId]); + + const handleSearch = (value: string) => { + setSearch(value); + }; + + const handleOpenChange = () => { + setSearch(''); + }; + + const handleChange = (id: string) => { + setUsername(listItems.find(item => item.id === id)?.username); + onChange(id); + }; + + const renderValue = () => { + return ( + + {username} + + ); + }; + + return ( + + ); +} diff --git a/src/components/input/WebsiteDateFilter.tsx b/src/components/input/WebsiteDateFilter.tsx index 18b4f13b..a76058ec 100644 --- a/src/components/input/WebsiteDateFilter.tsx +++ b/src/components/input/WebsiteDateFilter.tsx @@ -41,7 +41,7 @@ export function WebsiteDateFilter({ }), ); } else { - router.push(updateParams({ date, offset: undefined })); + router.push(updateParams({ date, offset: undefined, unit: undefined })); } }; diff --git a/src/components/messages.ts b/src/components/messages.ts index 712495d8..3d7388cd 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -245,7 +245,10 @@ export const labels = defineMessages({ tag: { id: 'label.tag', defaultMessage: 'Tag' }, segment: { id: 'label.segment', defaultMessage: 'Segment' }, cohort: { id: 'label.cohort', defaultMessage: 'Cohort' }, + minute: { id: 'label.minute', defaultMessage: 'Minute' }, + hour: { id: 'label.hour', defaultMessage: 'Hour' }, day: { id: 'label.day', defaultMessage: 'Day' }, + month: { id: 'label.month', defaultMessage: 'Month' }, date: { id: 'label.date', defaultMessage: 'Date' }, pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' }, create: { id: 'label.create', defaultMessage: 'Create' }, diff --git a/src/components/metrics/MetricCard.tsx b/src/components/metrics/MetricCard.tsx index d15bcf13..590fd5ac 100644 --- a/src/components/metrics/MetricCard.tsx +++ b/src/components/metrics/MetricCard.tsx @@ -25,7 +25,7 @@ export const MetricCard = ({ showChange = false, }: MetricCardProps) => { const diff = value - change; - const pct = ((value - diff) / diff) * 100; + const pct = diff !== 0 ? ((value - diff) / diff) * 100 : value !== 0 ? 100 : 0; const props = useSpring({ x: Number(value) || 0, from: { x: 0 } }); const changeProps = useSpring({ x: Number(pct) || 0, from: { x: 0 } }); diff --git a/src/index.ts b/src/index.ts index 907c5623..df164b9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,11 +19,13 @@ export * from '@/app/(main)/teams/TeamAddForm'; export * from '@/app/(main)/teams/TeamJoinForm'; export * from '@/app/(main)/teams/TeamLeaveButton'; export * from '@/app/(main)/teams/TeamLeaveForm'; +export * from '@/app/(main)/teams/TeamMemberAddForm'; export * from '@/app/(main)/teams/TeamProvider'; export * from '@/app/(main)/teams/TeamsAddButton'; export * from '@/app/(main)/teams/TeamsDataTable'; export * from '@/app/(main)/teams/TeamsHeader'; export * from '@/app/(main)/teams/TeamsJoinButton'; +export * from '@/app/(main)/teams/TeamsMemberAddButton'; export * from '@/app/(main)/teams/TeamsTable'; export * from '@/app/(main)/websites/[websiteId]/settings/WebsiteData'; export * from '@/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm'; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index bfc80a13..3da177c0 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -55,6 +55,7 @@ export const SESSION_COLUMNS = [ 'country', 'city', 'region', + 'distinctId', ]; export const SEGMENT_TYPES = { @@ -69,6 +70,7 @@ export const FILTER_COLUMNS = { referrer: 'referrer_domain', domain: 'referrer_domain', hostname: 'hostname', + distinctId: 'distinct_id', title: 'page_title', query: 'url_query', os: 'os', diff --git a/src/lib/date.ts b/src/lib/date.ts index 3c1fd1b7..91af88f6 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -9,6 +9,7 @@ import { differenceInCalendarMonths, differenceInCalendarWeeks, differenceInCalendarYears, + differenceInDays, differenceInHours, differenceInMinutes, endOfDay, @@ -136,7 +137,12 @@ export function parseDateValue(value: string) { return { num: +num, unit }; } -export function parseDateRange(value: string, locale = 'en-US', timezone?: string): DateRange { +export function parseDateRange( + value: string, + unitValue?: string, + locale = 'en-US', + timezone?: string, +): DateRange { if (typeof value !== 'string') { return null; } @@ -146,7 +152,7 @@ export function parseDateRange(value: string, locale = 'en-US', timezone?: strin const startDate = new Date(+startTime); const endDate = new Date(+endTime); - const unit = getMinimumUnit(startDate, endDate); + const unit = getMinimumUnit(startDate, endDate, true); return { startDate, @@ -169,14 +175,14 @@ export function parseDateRange(value: string, locale = 'en-US', timezone?: strin endDate: endOfHour(now), offset: 0, num: num || 1, - unit, + unit: unitValue || unit, value, }; case 'day': return { startDate: num ? subDays(startOfDay(now), num) : startOfDay(now), endDate: endOfDay(now), - unit: num ? 'day' : 'hour', + unit: unitValue ? unitValue : num ? 'day' : 'hour', offset: 0, num: num || 1, value, @@ -187,7 +193,7 @@ export function parseDateRange(value: string, locale = 'en-US', timezone?: strin ? subWeeks(startOfWeek(now, { locale: dateLocale }), num) : startOfWeek(now, { locale: dateLocale }), endDate: endOfWeek(now, { locale: dateLocale }), - unit: 'day', + unit: unitValue || 'day', offset: 0, num: num || 1, value, @@ -196,7 +202,7 @@ export function parseDateRange(value: string, locale = 'en-US', timezone?: strin return { startDate: num ? subMonths(startOfMonth(now), num) : startOfMonth(now), endDate: endOfMonth(now), - unit: num ? 'month' : 'day', + unit: unitValue ? unitValue : num ? 'month' : 'day', offset: 0, num: num || 1, value, @@ -205,7 +211,7 @@ export function parseDateRange(value: string, locale = 'en-US', timezone?: strin return { startDate: num ? subYears(startOfYear(now), num) : startOfYear(now), endDate: endOfYear(now), - unit: 'month', + unit: unitValue || 'month', offset: 0, num: num || 1, value, @@ -273,12 +279,20 @@ export function getAllowedUnits(startDate: Date, endDate: Date) { return index >= 0 ? units.splice(index) : []; } -export function getMinimumUnit(startDate: number | Date, endDate: number | Date) { +export function getMinimumUnit( + startDate: number | Date, + endDate: number | Date, + isDateRange: boolean = false, +) { if (differenceInMinutes(endDate, startDate) <= 60) { return 'minute'; - } else if (differenceInHours(endDate, startDate) <= 48) { + } else if ( + isDateRange + ? differenceInHours(endDate, startDate) <= 48 + : differenceInDays(endDate, startDate) <= 30 + ) { return 'hour'; - } else if (differenceInCalendarMonths(endDate, startDate) <= 6) { + } else if (differenceInCalendarMonths(endDate, startDate) <= 7) { return 'day'; } else if (differenceInCalendarMonths(endDate, startDate) <= 24) { return 'month'; diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index bfd007d1..cbabe03b 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -20,14 +20,6 @@ const PRISMA_LOG_OPTIONS = { }; const DATE_FORMATS = { - minute: 'YYYY-MM-DD HH24:MI:00', - hour: 'YYYY-MM-DD HH24:00:00', - day: 'YYYY-MM-DD HH24:00:00', - month: 'YYYY-MM-01 HH24:00:00', - year: 'YYYY-01-01 HH24:00:00', -}; - -const DATE_FORMATS_UTC = { minute: 'YYYY-MM-DD"T"HH24:MI:00"Z"', hour: 'YYYY-MM-DD"T"HH24:00:00"Z"', day: 'YYYY-MM-DD"T"HH24:00:00"Z"', @@ -52,7 +44,7 @@ function getDateSQL(field: string, unit: string, timezone?: string): string { return `to_char(date_trunc('${unit}', ${field} at time zone '${timezone}'), '${DATE_FORMATS[unit]}')`; } - return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS_UTC[unit]}')`; + return `to_char(date_trunc('${unit}', ${field}), '${DATE_FORMATS[unit]}')`; } function getDateWeeklySQL(field: string, timezone?: string) { diff --git a/src/lib/schema.ts b/src/lib/schema.ts index ac360a8e..a3c56a0f 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -36,6 +36,7 @@ export const filterParams = { city: z.string().optional(), tag: z.string().optional(), hostname: z.string().optional(), + distinctId: z.string().optional(), language: z.string().optional(), event: z.string().optional(), segment: z.uuid().optional(), @@ -89,6 +90,7 @@ export const fieldsParam = z.enum([ 'city', 'tag', 'hostname', + 'distinctId', 'language', 'event', ]); @@ -166,6 +168,7 @@ export const journeyReportSchema = z.object({ steps: z.coerce.number().min(2).max(7), startStep: z.string().optional(), endStep: z.string().optional(), + eventType: z.coerce.number().int().positive().optional(), }), }); diff --git a/src/queries/sql/events/getEventExpandedMetrics.ts b/src/queries/sql/events/getEventExpandedMetrics.ts index f03a347d..86bda850 100644 --- a/src/queries/sql/events/getEventExpandedMetrics.ts +++ b/src/queries/sql/events/getEventExpandedMetrics.ts @@ -58,7 +58,7 @@ async function relationalQuery( sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" from ( select - ${column} name, + ${column} as "name", website_event.session_id, website_event.visit_id, count(*) as "c", @@ -72,6 +72,7 @@ async function relationalQuery( ${filterQuery} group by name, website_event.session_id, website_event.visit_id ) as t + where name != '' group by name order by visitors desc, visits desc limit ${limit} diff --git a/src/queries/sql/getChannelExpandedMetrics.ts b/src/queries/sql/getChannelExpandedMetrics.ts index 33640d59..f674d182 100644 --- a/src/queries/sql/getChannelExpandedMetrics.ts +++ b/src/queries/sql/getChannelExpandedMetrics.ts @@ -89,7 +89,7 @@ async function relationalQuery( when ${toPostgresPositionClause('referrer_domain', EMAIL_DOMAINS)} or utm_medium ilike '%mail%' then 'email' when ${toPostgresPositionClause('referrer_domain', SHOPPING_DOMAINS)} or utm_medium ilike '%shop%' then concat(prefix, 'Shopping') when ${toPostgresPositionClause('referrer_domain', VIDEO_DOMAINS)} or utm_medium ilike '%video%' then concat(prefix, 'Video') - else '' end AS name, + else '' end as "name", session_id, visit_id, c, diff --git a/src/queries/sql/getRealtimeActivity.ts b/src/queries/sql/getRealtimeActivity.ts index 075b65e2..c847b6f7 100644 --- a/src/queries/sql/getRealtimeActivity.ts +++ b/src/queries/sql/getRealtimeActivity.ts @@ -30,7 +30,8 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { session.device, session.country, website_event.url_path as "urlPath", - website_event.referrer_domain as "referrerDomain" + website_event.referrer_domain as "referrerDomain", + website_event.hostname from website_event ${cohortQuery} inner join session @@ -65,7 +66,8 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters): Promis device, country, url_path as urlPath, - referrer_domain as referrerDomain + referrer_domain as referrerDomain, + hostname from website_event ${cohortQuery} where website_id = {websiteId:UUID} diff --git a/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts b/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts index 986d7d5a..ccb0be53 100644 --- a/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts +++ b/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts @@ -86,7 +86,7 @@ async function relationalQuery( sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" from ( select - ${column} as name, + ${column} as "name", website_event.session_id, website_event.visit_id, count(*) as "c", diff --git a/src/queries/sql/reports/getAttribution.ts b/src/queries/sql/reports/getAttribution.ts index 1d040781..29068f7d 100644 --- a/src/queries/sql/reports/getAttribution.ts +++ b/src/queries/sql/reports/getAttribution.ts @@ -52,8 +52,8 @@ async function relationalQuery( function getUTMQuery(utmColumn: string) { return ` select - coalesce(we.${utmColumn}, '') name, - ${currency ? 'sum(e.value)' : 'count(distinct we.session_id)'} value + coalesce(we.${utmColumn}, '') as "name", + ${currency ? 'sum(e.value)' : 'count(distinct we.session_id)'} as "value" from model m join website_event we on we.created_at = m.created_at @@ -128,7 +128,7 @@ async function relationalQuery( ` ${currency ? revenueEventQuery : eventQuery} ${getModelQuery(model)} - select coalesce(we.referrer_domain, '') name, + select coalesce(we.referrer_domain, '') as "name", ${currency ? 'sum(e.value)' : 'count(distinct we.session_id)'} value from model m join website_event we @@ -166,8 +166,8 @@ async function relationalQuery( when coalesce(li_fat_id, '') != '' then 'LinkedIn Ads' when coalesce(twclid, '') != '' then 'Twitter Ads (X)' else '' - end name, - ${currency ? 'sum(e.value)' : 'count(distinct we.session_id)'} value + end as "name", + ${currency ? 'sum(e.value)' : 'count(distinct we.session_id)'} as "value" from model m join website_event we on we.created_at = m.created_at diff --git a/src/queries/sql/reports/getJourney.ts b/src/queries/sql/reports/getJourney.ts index 21a7f22d..d12d371b 100644 --- a/src/queries/sql/reports/getJourney.ts +++ b/src/queries/sql/reports/getJourney.ts @@ -60,7 +60,7 @@ async function relationalQuery( endStepQuery: string; params: Record; } { - const params = {}; + const params: { startStep?: string; endStep?: string } = {}; let sequenceQuery = ''; let startStepQuery = ''; let endStepQuery = ''; @@ -172,7 +172,7 @@ async function clickhouseQuery( endStepQuery: string; params: Record; } { - const params = {}; + const params: { startStep?: string; endStep?: string } = {}; let sequenceQuery = ''; let startStepQuery = ''; let endStepQuery = ''; diff --git a/src/queries/sql/reports/getRevenue.ts b/src/queries/sql/reports/getRevenue.ts index d3bff6c9..30d7d7f1 100644 --- a/src/queries/sql/reports/getRevenue.ts +++ b/src/queries/sql/reports/getRevenue.ts @@ -76,8 +76,8 @@ async function relationalQuery( const country = await rawQuery( ` select - session.country as name, - sum(revenue) value + session.country as "name", + sum(revenue) as "value" from revenue ${joinQuery} join session @@ -176,8 +176,8 @@ async function clickhouseQuery( >( ` select - website_event.country as name, - sum(website_revenue.revenue) as value + website_event.country as "name", + sum(website_revenue.revenue) as "value" from website_revenue any left join ( select * diff --git a/src/queries/sql/sessions/getSessionActivity.ts b/src/queries/sql/sessions/getSessionActivity.ts index af31fca6..1ac7e6ff 100644 --- a/src/queries/sql/sessions/getSessionActivity.ts +++ b/src/queries/sql/sessions/getSessionActivity.ts @@ -29,6 +29,7 @@ async function relationalQuery(websiteId: string, sessionId: string, filters: Qu event_type as "eventType", event_name as "eventName", visit_id as "visitId", + hostname, event_id IN (select website_event_id from event_data where website_id = {{websiteId::uuid}} @@ -60,6 +61,7 @@ async function clickhouseQuery(websiteId: string, sessionId: string, filters: Qu event_type as eventType, event_name as eventName, visit_id as visitId, + hostname, event_id IN (select event_id from event_data where website_id = {websiteId:UUID} diff --git a/src/queries/sql/sessions/getSessionExpandedMetrics.ts b/src/queries/sql/sessions/getSessionExpandedMetrics.ts index 85c12939..6b85cd45 100644 --- a/src/queries/sql/sessions/getSessionExpandedMetrics.ts +++ b/src/queries/sql/sessions/getSessionExpandedMetrics.ts @@ -65,7 +65,7 @@ async function relationalQuery( sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" from ( select - ${column} name, + ${column} as "name", ${includeCountry ? 'country,' : ''} website_event.session_id, website_event.visit_id, @@ -82,6 +82,7 @@ async function relationalQuery( group by name, website_event.session_id, website_event.visit_id ${includeCountry ? ', country' : ''} ) as t + where name != '' group by name ${includeCountry ? ', country' : ''} order by visitors desc, visits desc