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

Reimplementation of types #17

Merged
merged 10 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ export const getColumns = (
<DataTableColumnHeader column={column} title="Message" />
),
},
{
accessorKey: "cause",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Cause" />
),
},
{
accessorKey: "stack",
header: "",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
import { describe, expect, vi, it, afterEach } from "vitest";
import { testApiHandler } from "next-test-api-route-handler";
import insertEvent from "~/db/insertEvent";
import * as appHandler from "./route";
import { Event } from "@codaco/analytics";
import { analyticsEvent } from "@codaco/analytics";

jest.mock("~/db/insertEvent", () => jest.fn());
vi.mock("~/db/insertEvent", () => {
return {
default: (eventData: unknown) => ({ data: eventData, error: null }),
};
});

describe("/api/event", () => {
it("should insert event to the database", async () => {
const eventData = {
afterEach(() => {
vi.resetAllMocks();
});
it("should insert a valid event to the database", async () => {
const eventData: analyticsEvent = {
type: "AppSetup",
metadata: {
details: "testing details",
path: "testing path",
},
countryISOCode: "US",
installationId: "21321546453213123",
timestamp: new Date().toString(),
};

(insertEvent as jest.Mock).mockImplementation(async (eventData: Event) => {
return { data: eventData, error: null };
});

await testApiHandler({
appHandler,
test: async ({ fetch }) => {
const response = await fetch({
method: "POST",
body: JSON.stringify(eventData),
});
expect(insertEvent).toHaveBeenCalledWith({
...eventData,
timestamp: new Date(eventData.timestamp),
});
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ event: eventData });
},
});
});

it("should return 400 if event is invalid", async () => {
const eventData = {
type: "InvalidEvent",
it("should insert a valid error into the database", async () => {
const eventData: analyticsEvent = {
type: "Error",
name: "TestError",
message: "Test message",
stack: "Test stack",
metadata: {
details: "testing details",
path: "testing path",
},
countryISOCode: "US",
installationId: "21321546453213123",
timestamp: new Date().toString(),
};

Expand All @@ -55,42 +60,24 @@ describe("/api/event", () => {
method: "POST",
body: JSON.stringify(eventData),
});
expect(response.status).toBe(400);
expect(await response.json()).toEqual({ error: "Invalid event" });
expect(response.status).toBe(200);
},
});
});

it("should return 500 if there is an error inserting the event to the database", async () => {
it("should return 400 if event is invalid", async () => {
const eventData = {
type: "AppSetup",
metadata: {
details: "testing details",
path: "testing path",
},
installationId: "21321546453213123",
timestamp: new Date().toString(),
type: "InvalidEvent",
};

(insertEvent as jest.Mock).mockImplementation(async (eventData: Event) => {
return { data: null, error: "Error inserting events" };
});

await testApiHandler({
appHandler,
test: async ({ fetch }) => {
const response = await fetch({
method: "POST",
body: JSON.stringify(eventData),
});
expect(insertEvent).toHaveBeenCalledWith({
...eventData,
timestamp: new Date(eventData.timestamp),
});
expect(response.status).toBe(500);
expect(await response.json()).toEqual({
error: "Error inserting events",
});
expect(response.status).toBe(400);
},
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { NextRequest, NextResponse } from "next/server";
import insertEvent from "~/db/insertEvent";
import { EventsSchema } from "@codaco/analytics";
import { AnalyticsEventSchema } from "@codaco/analytics";

// Allow CORS requests from anywhere.
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
Expand All @@ -12,32 +13,30 @@ export const runtime = "edge";

export async function POST(request: NextRequest) {
const event = (await request.json()) as unknown;
const parsedEvent = EventsSchema.safeParse(event);
const parsedEvent = AnalyticsEventSchema.safeParse(event);

if (parsedEvent.success === false) {
if (!parsedEvent.success) {
return NextResponse.json(
{ error: "Invalid event" },
{ status: 400, headers: corsHeaders }
);
}

try {
const result = await insertEvent({
...parsedEvent.data,
timestamp: new Date(parsedEvent.data.timestamp),
message: parsedEvent.data.error?.message,
name: parsedEvent.data.error?.name,
stack: parsedEvent.data.error?.stack,
});
if (result.error) throw new Error(result.error);

return NextResponse.json({ event }, { status: 200, headers: corsHeaders });
} catch (error) {
const formattedEvent = {
...parsedEvent.data,
timestamp: new Date(parsedEvent.data.timestamp), // Convert back into a date object
};

const result = await insertEvent(formattedEvent);

if (result.error) {
return NextResponse.json(
{ error: "Error inserting events" },
{ status: 500, headers: corsHeaders }
);
}

return NextResponse.json({ event }, { status: 200, headers: corsHeaders });
}

export async function OPTIONS() {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import {
timestamp,
} from "drizzle-orm/pg-core";

// Create a pgTable that maps to a table in your DB
export const eventsTable = pgTable(
"events",
{
id: serial("id").primaryKey(),
type: text("type").notNull(),
type: text("type").notNull(), // Todo: make this use pgEnum with the eventTypes array.
installationId: text("installationId").notNull(),
timestamp: timestamp("timestamp").notNull(),
isocode: text("isocode"),
countryISOCode: text("countryISOCode").notNull(),
message: text("message"),
name: text("name"),
stack: text("stack"),
cause: text("cause"),
metadata: json("metadata"),
},
(events) => {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ CREATE TABLE IF NOT EXISTS "events" (
"type" text NOT NULL,
"installationId" text NOT NULL,
"timestamp" timestamp NOT NULL,
"isocode" text,
"countryISOCode" text NOT NULL,
"message" text,
"name" text,
"stack" text,
"cause" text,
"metadata" json
);
--> statement-breakpoint
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "8314d6f3-51e8-44a2-a9ec-d3128c1a7775",
"id": "dd5e2f22-a389-41cc-98a4-d6b3a7a3feee",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "5",
"dialect": "pg",
Expand Down Expand Up @@ -32,11 +32,11 @@
"primaryKey": false,
"notNull": true
},
"isocode": {
"name": "isocode",
"countryISOCode": {
"name": "countryISOCode",
"type": "text",
"primaryKey": false,
"notNull": false
"notNull": true
},
"message": {
"name": "message",
Expand All @@ -56,6 +56,12 @@
"primaryKey": false,
"notNull": false
},
"cause": {
"name": "cause",
"type": "text",
"primaryKey": false,
"notNull": false
},
"metadata": {
"name": "metadata",
"type": "json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
{
"idx": 0,
"version": "5",
"when": 1706248816583,
"tag": "0000_yellow_the_watchers",
"when": 1707131657461,
"tag": "0000_overconfident_zzzax",
"breakpoints": true
}
]
Expand Down
File renamed without changes.
File renamed without changes.
13 changes: 7 additions & 6 deletions apps/analytics/package.json → apps/analytics-web/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "analytics",
"name": "analytics-web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test:event": "jest app/api/event/route.test.ts",
"test": "vitest run",
"test:watch": "vitest watch",
"generate": "npx drizzle-kit generate:pg",
"migrate": "npx tsx scripts/migrate.ts",
"seed": "pnpm run generate && pnpm run migrate && npx tsx scripts/seed.ts"
Expand Down Expand Up @@ -44,18 +45,18 @@
"@faker-js/faker": "^8.2.0",
"@next/eslint-plugin-next": "^13.4.19",
"@testing-library/react": "^14.1.2",
"@types/jest": "^29.5.11",
"@types/node": "^20.5.2",
"@types/papaparse": "^5.3.14",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^4.2.1",
"drizzle-kit": "^0.20.13",
"eslint-config-custom": "workspace:*",
"jest": "^29.7.0",
"jsdom": "^24.0.0",
"next-test-api-route-handler": "^4.0.3",
"tailwindcss": "^3.3.0",
"ts-jest": "^29.1.2",
"tsconfig": "workspace:*",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"vitest": "^1.2.2"
}
}
File renamed without changes.
File renamed without changes
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,43 @@ async function seedEvents() {

try {
for (let i = 0; i < 100; i++) {
const type = faker.helpers.arrayElement(eventTypes);
const type = faker.helpers.arrayElement([...eventTypes, "Error"]);
const installationId = faker.helpers.arrayElement(installationIds);
const timestamp = faker.date.recent();
const metadata = {
details: faker.lorem.sentence(),
path: faker.lorem.sentence(),
};
const isocode = faker.location.countryCode();
const countryISOCode = faker.location.countryCode();
const message = faker.lorem.sentence();
const name = faker.lorem.sentence();
const stack = faker.lorem.sentence();
const cause = faker.lorem.sentence();

const event: EventInsertType = {
const noneErrorEvent: EventInsertType = {
type,
metadata,
installationId,
timestamp,
metadata,
countryISOCode,
};

const errorEvent: EventInsertType = {
type,
installationId,
isocode,
timestamp,
metadata,
countryISOCode,
message,
name,
stack,
cause,
};

await db.insert(eventsTable).values(event).returning();
await db
.insert(eventsTable)
.values(type === "Error" ? errorEvent : noneErrorEvent)
.returning();
}
} catch (error) {
console.error("Error seeding events", error);
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default async function getRegionsTotals(): Promise<RegionTotal[]> {
const calculatedTotals: Record<string, number> = {};

for (const event of events) {
const isocode = event.isocode;
const isocode = event.countryISOCode;

if (isocode) {
calculatedTotals[isocode] = (calculatedTotals[isocode] || 0) + 1;
Expand Down
File renamed without changes.
7 changes: 7 additions & 0 deletions apps/analytics-web/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
test: {
environment: "node",
},
});
8 changes: 0 additions & 8 deletions apps/analytics/jest.config.js

This file was deleted.

Loading
Loading