web: filter events by name (all events and profile events)

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-12-13 11:08:10 +01:00
parent 13d7ad2a8c
commit 0a15a773e2
7 changed files with 93 additions and 45 deletions

View File

@@ -27,8 +27,8 @@ As of today (2023-12-12) I have more then 1.2 million events in PSQL and perform
- [ ] Active users (5min, 10min, 30min) - [ ] Active users (5min, 10min, 30min)
- [x] Save report to a specific dashboard - [x] Save report to a specific dashboard
- [x] View events in a list - [x] View events in a list
- [ ] Simple filters - [x] Simple filters
- [*] View profiles in a list - [x] View profiles in a list
- [ ] Invite users - [ ] Invite users
- [ ] Drag n Drop reports on dashboard - [ ] Drag n Drop reports on dashboard
- [x] Manage dashboards - [x] Manage dashboards
@@ -42,7 +42,7 @@ As of today (2023-12-12) I have more then 1.2 million events in PSQL and perform
### SDK ### SDK
- [*] Store duration on screen view events (can be done in backend as well) - [x] Store duration on screen view events (can be done in backend as well)
- [x] Create native sdk - [x] Create native sdk
- [x] Handle sessions - [x] Handle sessions
- [x] Create web sdk - [x] Create web sdk

View File

@@ -1,7 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { DataTable } from '@/components/DataTable'; import { DataTable } from '@/components/DataTable';
import { Pagination } from '@/components/Pagination';
import type { PaginationProps } from '@/components/Pagination';
import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table'; import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table';
import { useOrganizationParams } from '@/hooks/useOrganizationParams'; import { useOrganizationParams } from '@/hooks/useOrganizationParams';
@@ -17,10 +15,9 @@ const columnHelper =
interface EventsTableProps { interface EventsTableProps {
data: RouterOutputs['event']['list']; data: RouterOutputs['event']['list'];
pagination: PaginationProps;
} }
export function EventsTable({ data, pagination }: EventsTableProps) { export function EventsTable({ data }: EventsTableProps) {
const params = useOrganizationParams(); const params = useOrganizationParams();
const columns = useMemo(() => { const columns = useMemo(() => {
return [ return [
@@ -94,11 +91,5 @@ export function EventsTable({ data, pagination }: EventsTableProps) {
]; ];
}, [params]); }, [params]);
return ( return <DataTable data={data} columns={columns} />;
<>
<Pagination {...pagination} />
<DataTable data={data} columns={columns} />
<Pagination {...pagination} />
</>
);
} }

View File

@@ -5,7 +5,7 @@ import { Command, CommandGroup, CommandItem } from '@/components/ui/command';
import { Checkbox } from './checkbox'; import { Checkbox } from './checkbox';
import { Input } from './input'; import { Input } from './input';
type IValue = string | number | boolean | null; type IValue = any;
type IItem = Record<'value' | 'label', IValue>; type IItem = Record<'value' | 'label', IValue>;
interface ComboboxAdvancedProps { interface ComboboxAdvancedProps {
@@ -60,7 +60,7 @@ export function ComboboxAdvanced({
); );
}; };
const renderUnknownItem = (value: string | number | null | boolean) => { const renderUnknownItem = (value: IValue) => {
const item = items.find((item) => item.value === value); const item = items.find((item) => item.value === value);
return item ? renderItem(item) : renderItem({ value, label: value }); return item ? renderItem(item) : renderItem({ value, label: value });
}; };
@@ -92,7 +92,7 @@ export function ComboboxAdvanced({
</button> </button>
<div className="relative mt-2"> <div className="relative mt-2">
{open && ( {open && (
<div className="max-h-80 absolute w-full z-10 top-0 rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in"> <div className="max-h-80 min-w-[300px] absolute w-full z-10 top-0 rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in">
<CommandGroup className="max-h-80 overflow-auto"> <CommandGroup className="max-h-80 overflow-auto">
<div className="p-1 mb-2"> <div className="p-1 mb-2">
<Input <Input

View File

@@ -183,6 +183,7 @@ export const pushModal = <
) => ) =>
emitter.emit('push', { emitter.emit('push', {
name, name,
// @ts-expect-error
props: Array.isArray(rest) && rest[0] ? rest[0] : {}, props: Array.isArray(rest) && rest[0] ? rest[0] : {},
}); });
export const replaceModal = < export const replaceModal = <
@@ -194,6 +195,7 @@ export const replaceModal = <
) => ) =>
emitter.emit('replace', { emitter.emit('replace', {
name, name,
// @ts-expect-error
props: Array.isArray(rest) && rest[0] ? rest[0] : {}, props: Array.isArray(rest) && rest[0] ? rest[0] : {},
}); });
export const popModal = (name?: StateItem['name']) => export const popModal = (name?: StateItem['name']) =>

View File

@@ -1,17 +1,20 @@
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { Container } from '@/components/Container'; import { Container } from '@/components/Container';
import { EventsTable } from '@/components/events/EventsTable'; import { EventsTable } from '@/components/events/EventsTable';
import { MainLayout } from '@/components/layouts/MainLayout'; import { MainLayout } from '@/components/layouts/MainLayout';
import { PageTitle } from '@/components/PageTitle'; import { PageTitle } from '@/components/PageTitle';
import { usePagination } from '@/components/Pagination'; import { Pagination, usePagination } from '@/components/Pagination';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { useOrganizationParams } from '@/hooks/useOrganizationParams'; import { useOrganizationParams } from '@/hooks/useOrganizationParams';
import { api } from '@/utils/api'; import { api } from '@/utils/api';
export default function Events() { export default function Events() {
const pagination = usePagination(); const pagination = usePagination();
const params = useOrganizationParams(); const params = useOrganizationParams();
const [eventFilters, setEventFilters] = useState<string[]>([]);
const eventsQuery = api.event.list.useQuery( const eventsQuery = api.event.list.useQuery(
{ {
events: eventFilters,
projectSlug: params.project, projectSlug: params.project,
...pagination, ...pagination,
}, },
@@ -21,11 +24,32 @@ export default function Events() {
); );
const events = useMemo(() => eventsQuery.data ?? [], [eventsQuery]); const events = useMemo(() => eventsQuery.data ?? [], [eventsQuery]);
const filterEventsQuery = api.chart.events.useQuery({
projectSlug: params.project,
});
const filterEvents = (filterEventsQuery.data ?? []).map((item) => ({
value: item.name,
label: item.name,
}));
return ( return (
<MainLayout> <MainLayout>
<Container> <Container>
<PageTitle>Events</PageTitle> <PageTitle>Events</PageTitle>
<EventsTable data={events} pagination={pagination} />
<div className="flex justify-between items-center">
<div>
<ComboboxAdvanced
items={filterEvents}
selected={eventFilters}
setSelected={setEventFilters}
placeholder="Filter by event"
/>
</div>
<Pagination {...pagination} />
</div>
<EventsTable data={events} />
<Pagination {...pagination} />
</Container> </Container>
</MainLayout> </MainLayout>
); );

View File

@@ -1,12 +1,12 @@
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { Container } from '@/components/Container'; import { Container } from '@/components/Container';
import { EventsTable } from '@/components/events/EventsTable'; import { EventsTable } from '@/components/events/EventsTable';
import { MainLayout } from '@/components/layouts/MainLayout'; import { MainLayout } from '@/components/layouts/MainLayout';
import { PageTitle } from '@/components/PageTitle'; import { PageTitle } from '@/components/PageTitle';
import { usePagination } from '@/components/Pagination'; import { Pagination, usePagination } from '@/components/Pagination';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { useOrganizationParams } from '@/hooks/useOrganizationParams'; import { useOrganizationParams } from '@/hooks/useOrganizationParams';
import { useQueryParams } from '@/hooks/useQueryParams'; import { useQueryParams } from '@/hooks/useQueryParams';
import { createServerSideProps } from '@/server/getServerSideProps';
import { api } from '@/utils/api'; import { api } from '@/utils/api';
import { getProfileName } from '@/utils/getters'; import { getProfileName } from '@/utils/getters';
import { z } from 'zod'; import { z } from 'zod';
@@ -19,6 +19,14 @@ export default function ProfileId() {
profileId: z.string(), profileId: z.string(),
}) })
); );
const [eventFilters, setEventFilters] = useState<string[]>([]);
const filterEventsQuery = api.chart.events.useQuery({
projectSlug: params.project,
});
const filterEvents = (filterEventsQuery.data ?? []).map((item) => ({
value: item.name,
label: item.name,
}));
const profileQuery = api.profile.get.useQuery({ const profileQuery = api.profile.get.useQuery({
id: profileId, id: profileId,
}); });
@@ -26,6 +34,7 @@ export default function ProfileId() {
{ {
projectSlug: params.project, projectSlug: params.project,
profileId, profileId,
events: eventFilters,
...pagination, ...pagination,
}, },
{ {
@@ -40,7 +49,19 @@ export default function ProfileId() {
<Container> <Container>
<PageTitle>{getProfileName(profile)}</PageTitle> <PageTitle>{getProfileName(profile)}</PageTitle>
<pre>{JSON.stringify(profile?.properties, null, 2)}</pre> <pre>{JSON.stringify(profile?.properties, null, 2)}</pre>
<EventsTable data={events} pagination={pagination} /> <div className="flex justify-between items-center">
<div>
<ComboboxAdvanced
items={filterEvents}
selected={eventFilters}
setSelected={setEventFilters}
placeholder="Filter by event"
/>
</div>
<Pagination {...pagination} />
</div>
<EventsTable data={events} />
<Pagination {...pagination} />
</Container> </Container>
</MainLayout> </MainLayout>
); );

View File

@@ -16,9 +16,11 @@ export const eventRouter = createTRPCRouter({
take: z.number().default(100), take: z.number().default(100),
skip: z.number().default(0), skip: z.number().default(0),
profileId: z.string().optional(), profileId: z.string().optional(),
events: z.array(z.string()).optional(),
}) })
) )
.query(async ({ input: { take, skip, projectSlug, profileId } }) => { .query(
async ({ input: { take, skip, projectSlug, profileId, events } }) => {
const project = await db.project.findUniqueOrThrow({ const project = await db.project.findUniqueOrThrow({
where: { where: {
slug: projectSlug, slug: projectSlug,
@@ -30,6 +32,13 @@ export const eventRouter = createTRPCRouter({
where: { where: {
project_id: project.id, project_id: project.id,
profile_id: profileId, profile_id: profileId,
...(events && events.length > 0
? {
name: {
in: events,
},
}
: {}),
}, },
orderBy: { orderBy: {
createdAt: 'desc', createdAt: 'desc',
@@ -38,5 +47,6 @@ export const eventRouter = createTRPCRouter({
profile: true, profile: true,
}, },
}); });
}), }
),
}); });