Skip to content

Commit

Permalink
add filter for event table and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkarimoff committed Jan 26, 2024
1 parent 85dcf4a commit 16eba2c
Show file tree
Hide file tree
Showing 22 changed files with 293 additions and 153 deletions.
17 changes: 13 additions & 4 deletions apps/analytics/app/_components/analytics/AnalyticsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,28 @@ import TotalInterviewsCompletedCard from "./cards/TotalInterviewsCompletedCard";
import TotalInterviewsStartedCard from "./cards/TotalInterviewsStartedCard";
import TotalProtocolsInstalledCard from "./cards/TotalProtocolsInstalledCard";
import RegionsTable from "./RegionsTable/RegionsTable";
import getEvents from "~/db/getEvents";
import TotalErrorsCard from "./cards/TotalErrorsCard";
import TotalDataExported from "./cards/TotalDataExported";

export default async function AnalyticsView() {
const events = await getEvents();

export default function AnalyticsView() {
return (
<div className="space-y-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<TotalAppsCard />
<TotalProtocolsInstalledCard />
<TotalInterviewsStartedCard />
<TotalInterviewsCompletedCard />
<TotalDataExported />
<TotalErrorsCard />
</div>
<div className="grid gap-4 lg:grid-cols-2 sm:grid-cols-1">
<EventsTable />
<Card>
<div className="grid gap-4 lg:grid-cols-3 md:grid-cols-2 sm:grid-cols-1">
<div className="lg:col-span-2">
<EventsTable events={events} />
</div>
<Card className="h-fit">
<CardHeader>Regions</CardHeader>
<CardContent>
<RegionsTable />
Expand Down
23 changes: 23 additions & 0 deletions apps/analytics/app/_components/analytics/EventsTable/Columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ColumnDef } from "@tanstack/react-table";
import { DataTableColumnHeader } from "~/components/DataTable/column-header";
import { MetadataDialog } from "~/components/MetadataDialog";
import type { Event } from "~/db/getEvents";
import { StackTraceDialog } from "./StackTraceDialog";

export const columns: ColumnDef<Event>[] = [
{
Expand Down Expand Up @@ -32,6 +33,28 @@ export const columns: ColumnDef<Event>[] = [
return <div className="break-all">{row.original.installationId}</div>;
},
},
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
},
{
accessorKey: "message",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Message" />
),
},
{
accessorKey: "stack",
header: "",
cell: ({ row }) =>
row.original.stack && (
<div className="min-w-max">
<StackTraceDialog error={row.original} />
</div>
),
},
{
accessorKey: "metadata",
header: "",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
// EventsTable.tsx
"use client";

import { useEffect, useMemo, useState } from "react";
import { DataTable } from "~/components/DataTable/data-table";
import ExportButton from "~/components/ExportButton";
import getEvents from "~/db/getEvents";
import { Event } from "~/db/getEvents";
import { columns } from "./Columns";
import TableFilter from "./TableFilter";

export type EventType = {
text: string;
isSelected: boolean;
};

export default function EventsTable({ events }: { events: Event[] }) {
const [eventTypes, setEventTypes] = useState<EventType[]>([]);

useEffect(() => {
const eventTypesMap = new Map<string, EventType>();
events.forEach((event) =>
eventTypesMap.set(event.type, { text: event.type, isSelected: true })
);

export default async function EventsTable() {
const events = await getEvents();
setEventTypes([...Array.from(eventTypesMap.values())]);
}, [events]);

const filteredEvents = useMemo(() => {
const filters = eventTypes
.filter((type) => type.isSelected)
.map((type) => type.text);

return events.filter((event) => filters.includes(event.type));
}, [eventTypes, events]);

return (
<div>
<div className="flex justify-between">
<TableFilter eventTypes={eventTypes} setEventTypes={setEventTypes} />

<div className="flex justify-between items-center mt-2">
<h2>Events</h2>
<ExportButton data={events} filename="events.csv" />
</div>

<div className="mt-4">
<DataTable columns={columns} data={events} pagination={true} />
<div className="mt-2">
<DataTable columns={columns} data={filteredEvents} pagination={true} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DialogButton } from "~/components/DialogButton";
import { type ErrorEvent } from "~/db/getErrors";
import { Event } from "~/db/getEvents";

export function StackTraceDialog({ error }: { error: ErrorEvent }) {
export function StackTraceDialog({ error }: { error: Event }) {
return (
<DialogButton
buttonLabel="Stack Trace"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client";

import { Button } from "~/components/ui/button";
import { Checkbox } from "~/components/ui/checkbox";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { EventType } from "./EventsTable";
import { useEffect, useState, type Dispatch, type SetStateAction } from "react";

type TableFilterProps = {
eventTypes: EventType[];
setEventTypes: Dispatch<SetStateAction<EventType[]>>;
};

const TableFilter = ({ eventTypes, setEventTypes }: TableFilterProps) => {
const [allSelected, setAllSelected] = useState(true);

useEffect(() => {
const updatedEventTypes = eventTypes.map((t) => ({
...t,
isSelected: allSelected,
}));
setEventTypes(updatedEventTypes);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allSelected]);

const handleCheckedChange = (value: boolean, currentType: string) => {
const updatedEventTypes = eventTypes.map((t) => {
if (t.text === currentType) {
return { ...t, isSelected: value };
}
return t;
});

// If all event types are selected, set allSelected to true
if (updatedEventTypes.every((t) => t.isSelected)) {
setAllSelected(true);
return;
}

// If no event types are selected, set allSelected to false
if (updatedEventTypes.every((t) => !t.isSelected)) {
setAllSelected(false);
return;
}

setEventTypes(updatedEventTypes);
};

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Event Types</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52 ml-12">
<DropdownMenuLabel>Select events</DropdownMenuLabel>
<DropdownMenuSeparator />

<div className="space-y-3">
<label className="text-sm flex items-center gap-3 pl-2 transition-colors hover:bg-muted p-1 rounded-md">
<Checkbox
checked={allSelected}
onCheckedChange={(val) => setAllSelected(Boolean(val))}
/>
<span>All</span>
</label>

{eventTypes.map((type) => (
<label
key={type.text}
className="text-sm flex items-center gap-3 pl-2 transition-colors hover:bg-muted p-1 rounded-md"
>
<Checkbox
checked={type.isSelected}
onCheckedChange={(val) =>
handleCheckedChange(Boolean(val), type.text)
}
/>
<span>{type.text}</span>
</label>
))}
</div>
</DropdownMenuContent>
</DropdownMenu>
);
};

export default TableFilter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getTotalInterviewsStarted } from "~/utils/getTotalInterviewsStarted";
import { SummaryCard } from "~/components/SummaryCard";

const TotalDataExported = async () => {
const totalInterviewsStarted = await getTotalInterviewsStarted();
return (
<SummaryCard
title="Data Exported"
value={totalInterviewsStarted}
description="Total data exported across all instances of Fresco"
/>
);
};

export default TotalDataExported;
15 changes: 15 additions & 0 deletions apps/analytics/app/_components/analytics/cards/TotalErrorsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SummaryCard } from "~/components/SummaryCard";
import { getTotalErrors } from "~/utils/getTotalErrors";

const TotalErrorsCard = async () => {
const totalErrors = await getTotalErrors();
return (
<SummaryCard
title="Number of Errors"
value={totalErrors}
description="Total number of errors sent from all instances of Fresco"
/>
);
};

export default TotalErrorsCard;
56 changes: 0 additions & 56 deletions apps/analytics/app/_components/errors/ErrorsTable/Columns.tsx

This file was deleted.

15 changes: 0 additions & 15 deletions apps/analytics/app/_components/errors/ErrorsTable/ErrorsTable.tsx

This file was deleted.

13 changes: 0 additions & 13 deletions apps/analytics/app/_components/errors/ErrorsView.tsx

This file was deleted.

10 changes: 0 additions & 10 deletions apps/analytics/app/_components/errors/cards/TotalErrorsCard.tsx

This file was deleted.

15 changes: 1 addition & 14 deletions apps/analytics/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { UserButton } from "@clerk/nextjs";
import AnalyticsView from "~/app/_components/analytics/AnalyticsView";
import ErrorsView from "~/app/_components/errors/ErrorsView";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import UserManagementDialog from "./_components/users/UserManagementDialog";

export default function DashboardPage() {
Expand All @@ -16,18 +14,7 @@ export default function DashboardPage() {
</div>
</div>

<Tabs defaultValue="analytics" className="space-y-4">
<TabsList>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="errors">Errors</TabsTrigger>
</TabsList>
<TabsContent value="analytics">
<AnalyticsView />
</TabsContent>
<TabsContent value="errors">
<ErrorsView />
</TabsContent>
</Tabs>
<AnalyticsView />
</div>
</main>
);
Expand Down
Loading

0 comments on commit 16eba2c

Please sign in to comment.