Skip to content

Commit

Permalink
Merge pull request #16 from complexdatacollective/refactor/event-types
Browse files Browse the repository at this point in the history
Refactor/event types
  • Loading branch information
jthrilly authored Feb 1, 2024
2 parents faa2b6f + 3940b29 commit ef62e4e
Show file tree
Hide file tree
Showing 43 changed files with 3,014 additions and 558 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
115 changes: 80 additions & 35 deletions apps/analytics/app/_components/analytics/EventsTable/Columns.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,84 @@
"use client";
import { DataTableColumnHeader } from "~/components/DataTable/column-header";

import { ColumnDef } from "@tanstack/react-table";
import type { Event } from "~/db/schema";
import { MetadataDialog } from "./MetadataDialog";
export const columns: ColumnDef<Event>[] = [
{
accessorKey: "type",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Event" />
),
},
{
accessorKey: "timestamp",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Timestamp" />
),
},
{
accessorKey: "installationid",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Installation Id" />
),
cell: ({ row }) => {
return <div className="break-all">{row.original.installationid}</div>;
},
},
{
accessorKey: "stacktrace",
header: "",
cell: ({ row }) => {
return (
<div className="min-w-max">
<MetadataDialog event={row.original} />
import { type Dispatch, type SetStateAction } from "react";
import { DataTableColumnHeader } from "~/components/DataTable/column-header";
import { MetadataDialog } from "~/components/MetadataDialog";
import type { Event } from "~/db/getEvents";
import { type EventType } from "./EventsTable";
import { StackTraceDialog } from "./StackTraceDialog";
import TableFilter from "./TableFilter";

export const getColumns = (
eventTypes: EventType[],
setEventTypes: Dispatch<SetStateAction<EventType[]>>
) => {
const columns: ColumnDef<Event>[] = [
{
accessorKey: "type",
header: ({ column }) => (
<div className="flex space-x-4">
<TableFilter eventTypes={eventTypes} setEventTypes={setEventTypes} />
<DataTableColumnHeader column={column} />
</div>
);
),
},
{
accessorKey: "timestamp",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Timestamp" />
),
cell: ({ row }) => {
return (
<div className="break-all">
{row.original.timestamp.toUTCString()}
</div>
);
},
},
{
accessorKey: "installationId",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Installation Id" />
),
cell: ({ row }) => {
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: "",
cell: ({ row }) => {
return (
<div className="min-w-max">
<MetadataDialog event={row.original} />
</div>
);
},
},
},
];
];

return columns;
};
Original file line number Diff line number Diff line change
@@ -1,22 +1,49 @@
// EventsTable.tsx
import React from "react";
"use client";

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

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 })
);

setEventTypes([...Array.from(eventTypesMap.values())]);
}, [events]);

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

export default async function EventsTable() {
const events = await getEvents();
return events.filter((event) => filters.includes(event.type));
}, [eventTypes, events]);

return (
<div>
<div className="flex justify-between">
<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={getColumns(eventTypes, setEventTypes)}
data={filteredEvents}
pagination={true}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DialogButton } from "~/components/DialogButton";
import { Event } from "~/db/getEvents";

export function StackTraceDialog({ error }: { error: Event }) {
return (
<DialogButton
buttonLabel="Stack Trace"
title="Stack Trace"
description={error.message!}
content={error.stack}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

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

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

const TableFilter = ({ eventTypes, setEventTypes }: TableFilterProps) => {
const [options, setOptions] = useState<EventType[]>(eventTypes);

const toggleOption = (option: string) => {
setOptions((prevState) =>
prevState.map((t) =>
t.text === option ? { ...t, isSelected: !t.isSelected } : t
)
);
};

const toggleAllOptions = (isSelected: boolean) => {
setOptions((prevState) => prevState.map((t) => ({ ...t, isSelected })));
};

const isAllSelected = options.every((option) => option.isSelected);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="text-sm" size={"sm"} variant="outline">
Type
</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={isAllSelected}
onCheckedChange={() => toggleAllOptions(!isAllSelected)}
/>
<span>All</span>
</label>
<DropdownMenuSeparator />

{options.map((option) => (
<label
key={option.text}
className="text-sm flex items-center gap-3 pl-2 transition-colors hover:bg-muted p-1 rounded-md"
>
<Checkbox
checked={option.isSelected}
onCheckedChange={() => toggleOption(option.text)}
/>
<span>{option.text}</span>
</label>
))}

<Button
onClick={() => setEventTypes(options)}
className="float-right"
size={"sm"}
>
Apply
</Button>
</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;
48 changes: 0 additions & 48 deletions apps/analytics/app/_components/errors/ErrorsTable/Columns.tsx

This file was deleted.

Loading

0 comments on commit ef62e4e

Please sign in to comment.