Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/event types #16

Merged
merged 23 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e820d6d
Add metadata field to AnalyticsEventBase and make error a js Error
mrkarimoff Jan 24, 2024
c65d246
remove unused imports in ErrorsTable and add error details and path t…
mrkarimoff Jan 25, 2024
06efaf3
remove console logs from createRouteHandler function
mrkarimoff Jan 25, 2024
db3b0f9
wip: add AnalyticsEventExtensible
mrkarimoff Jan 25, 2024
e6a67da
remove AnalyticsEventExtensible
mrkarimoff Jan 25, 2024
a1ac508
Update version to 3.0.0 in analytics package.json
mrkarimoff Jan 25, 2024
60f94b6
wip: trying to integrate drizzle
mrkarimoff Jan 25, 2024
13e63a1
integrate Drizzle and update dashboard
mrkarimoff Jan 26, 2024
de1de4e
Refactor DialogButton and MetadataDialog components
mrkarimoff Jan 26, 2024
85dcf4a
fix timestamp render
mrkarimoff Jan 26, 2024
16eba2c
add filter for event table and refactor
mrkarimoff Jan 26, 2024
d4ba4e2
test and fix event insertion
mrkarimoff Jan 29, 2024
27cd7a3
add document reference to ES6 change
mrkarimoff Jan 29, 2024
6274ebf
fix table filter issue and put it on the table header
mrkarimoff Jan 29, 2024
8a85637
remove testing file
mrkarimoff Jan 29, 2024
f01c5af
wip: test api route handler
mrkarimoff Jan 30, 2024
962a058
add tests for api/event/route.ts and use zod schema to validate request
mrkarimoff Jan 30, 2024
f5919dc
Remove node-mocks-http dependency
mrkarimoff Jan 30, 2024
394b9a6
add 'Apply' button for event filters
mrkarimoff Jan 31, 2024
4440719
Merge branch 'main' into refactor/event-types
mrkarimoff Jan 31, 2024
361ce58
move EventsSchema and types into the analytics package
mrkarimoff Jan 31, 2024
cc8e4e1
bump analytics package version to 3.1.0
mrkarimoff Jan 31, 2024
3940b29
Update tsconfig.json - remove es6 so that app can inherit from es2020…
jthrilly Feb 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading