web: filter events by name (all events and profile events)
This commit is contained in:
@@ -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)
|
||||
- [x] Save report to a specific dashboard
|
||||
- [x] View events in a list
|
||||
- [ ] Simple filters
|
||||
- [*] View profiles in a list
|
||||
- [x] Simple filters
|
||||
- [x] View profiles in a list
|
||||
- [ ] Invite users
|
||||
- [ ] Drag n Drop reports on dashboard
|
||||
- [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
|
||||
|
||||
- [*] 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] Handle sessions
|
||||
- [x] Create web sdk
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { DataTable } from '@/components/DataTable';
|
||||
import { Pagination } from '@/components/Pagination';
|
||||
import type { PaginationProps } from '@/components/Pagination';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@/components/ui/table';
|
||||
import { useOrganizationParams } from '@/hooks/useOrganizationParams';
|
||||
@@ -17,10 +15,9 @@ const columnHelper =
|
||||
|
||||
interface EventsTableProps {
|
||||
data: RouterOutputs['event']['list'];
|
||||
pagination: PaginationProps;
|
||||
}
|
||||
|
||||
export function EventsTable({ data, pagination }: EventsTableProps) {
|
||||
export function EventsTable({ data }: EventsTableProps) {
|
||||
const params = useOrganizationParams();
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
@@ -94,11 +91,5 @@ export function EventsTable({ data, pagination }: EventsTableProps) {
|
||||
];
|
||||
}, [params]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pagination {...pagination} />
|
||||
<DataTable data={data} columns={columns} />
|
||||
<Pagination {...pagination} />
|
||||
</>
|
||||
);
|
||||
return <DataTable data={data} columns={columns} />;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Command, CommandGroup, CommandItem } from '@/components/ui/command';
|
||||
import { Checkbox } from './checkbox';
|
||||
import { Input } from './input';
|
||||
|
||||
type IValue = string | number | boolean | null;
|
||||
type IValue = any;
|
||||
type IItem = Record<'value' | 'label', IValue>;
|
||||
|
||||
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);
|
||||
return item ? renderItem(item) : renderItem({ value, label: value });
|
||||
};
|
||||
@@ -92,7 +92,7 @@ export function ComboboxAdvanced({
|
||||
</button>
|
||||
<div className="relative mt-2">
|
||||
{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">
|
||||
<div className="p-1 mb-2">
|
||||
<Input
|
||||
|
||||
@@ -183,6 +183,7 @@ export const pushModal = <
|
||||
) =>
|
||||
emitter.emit('push', {
|
||||
name,
|
||||
// @ts-expect-error
|
||||
props: Array.isArray(rest) && rest[0] ? rest[0] : {},
|
||||
});
|
||||
export const replaceModal = <
|
||||
@@ -194,6 +195,7 @@ export const replaceModal = <
|
||||
) =>
|
||||
emitter.emit('replace', {
|
||||
name,
|
||||
// @ts-expect-error
|
||||
props: Array.isArray(rest) && rest[0] ? rest[0] : {},
|
||||
});
|
||||
export const popModal = (name?: StateItem['name']) =>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Container } from '@/components/Container';
|
||||
import { EventsTable } from '@/components/events/EventsTable';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
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 { api } from '@/utils/api';
|
||||
|
||||
export default function Events() {
|
||||
const pagination = usePagination();
|
||||
const params = useOrganizationParams();
|
||||
const [eventFilters, setEventFilters] = useState<string[]>([]);
|
||||
const eventsQuery = api.event.list.useQuery(
|
||||
{
|
||||
events: eventFilters,
|
||||
projectSlug: params.project,
|
||||
...pagination,
|
||||
},
|
||||
@@ -21,11 +24,32 @@ export default function Events() {
|
||||
);
|
||||
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 (
|
||||
<MainLayout>
|
||||
<Container>
|
||||
<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>
|
||||
</MainLayout>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Container } from '@/components/Container';
|
||||
import { EventsTable } from '@/components/events/EventsTable';
|
||||
import { MainLayout } from '@/components/layouts/MainLayout';
|
||||
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 { useQueryParams } from '@/hooks/useQueryParams';
|
||||
import { createServerSideProps } from '@/server/getServerSideProps';
|
||||
import { api } from '@/utils/api';
|
||||
import { getProfileName } from '@/utils/getters';
|
||||
import { z } from 'zod';
|
||||
@@ -19,6 +19,14 @@ export default function ProfileId() {
|
||||
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({
|
||||
id: profileId,
|
||||
});
|
||||
@@ -26,6 +34,7 @@ export default function ProfileId() {
|
||||
{
|
||||
projectSlug: params.project,
|
||||
profileId,
|
||||
events: eventFilters,
|
||||
...pagination,
|
||||
},
|
||||
{
|
||||
@@ -40,7 +49,19 @@ export default function ProfileId() {
|
||||
<Container>
|
||||
<PageTitle>{getProfileName(profile)}</PageTitle>
|
||||
<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>
|
||||
</MainLayout>
|
||||
);
|
||||
|
||||
@@ -16,27 +16,37 @@ export const eventRouter = createTRPCRouter({
|
||||
take: z.number().default(100),
|
||||
skip: z.number().default(0),
|
||||
profileId: z.string().optional(),
|
||||
events: z.array(z.string()).optional(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { take, skip, projectSlug, profileId } }) => {
|
||||
const project = await db.project.findUniqueOrThrow({
|
||||
where: {
|
||||
slug: projectSlug,
|
||||
},
|
||||
});
|
||||
return db.event.findMany({
|
||||
take,
|
||||
skip,
|
||||
where: {
|
||||
project_id: project.id,
|
||||
profile_id: profileId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
include: {
|
||||
profile: true,
|
||||
},
|
||||
});
|
||||
}),
|
||||
.query(
|
||||
async ({ input: { take, skip, projectSlug, profileId, events } }) => {
|
||||
const project = await db.project.findUniqueOrThrow({
|
||||
where: {
|
||||
slug: projectSlug,
|
||||
},
|
||||
});
|
||||
return db.event.findMany({
|
||||
take,
|
||||
skip,
|
||||
where: {
|
||||
project_id: project.id,
|
||||
profile_id: profileId,
|
||||
...(events && events.length > 0
|
||||
? {
|
||||
name: {
|
||||
in: events,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
include: {
|
||||
profile: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user