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)
|
- [ ] 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
|
||||||
|
|||||||
@@ -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} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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']) =>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user