From 49106c9abaf0594336c7d32adff6f1c53741009d Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Tue, 1 Aug 2023 22:14:41 +0200 Subject: [PATCH] chore: add combobox to regions (#119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add combobox to regions * fix regions * fix build * 🚀 update * 🔥 fix --------- Co-authored-by: Thibault Le Ouay --- apps/web/src/app/api/checker/cron/_cron.ts | 31 +- .../src/app/api/checker/regions/_checker.ts | 2 +- .../app/api/checker/regions/random/route.ts | 17 + .../monitors/_components/action-button.tsx | 2 +- .../[workspaceSlug]/monitors/page.tsx | 2 +- .../[workspaceSlug]/status-pages/page.tsx | 3 - .../src/components/forms/montitor-form.tsx | 93 ++- packages/api/src/router/monitor.ts | 9 +- .../db/drizzle/0000_lively_master_chief.sql | 17 - packages/db/drizzle/0001_brainy_beast.sql | 2 + packages/db/drizzle/meta/0001_snapshot.json | 564 ++++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + packages/db/src/migrate.mts | 1 - packages/db/src/schema/monitor.ts | 29 + 14 files changed, 741 insertions(+), 38 deletions(-) create mode 100644 apps/web/src/app/api/checker/regions/random/route.ts create mode 100644 packages/db/drizzle/0001_brainy_beast.sql create mode 100644 packages/db/drizzle/meta/0001_snapshot.json diff --git a/apps/web/src/app/api/checker/cron/_cron.ts b/apps/web/src/app/api/checker/cron/_cron.ts index e14e380c..2b58fa52 100644 --- a/apps/web/src/app/api/checker/cron/_cron.ts +++ b/apps/web/src/app/api/checker/cron/_cron.ts @@ -1,10 +1,11 @@ import { Client } from "@upstash/qstash/cloudflare"; -import type { z } from "zod"; +import { z } from "zod"; import { and, db, eq } from "@openstatus/db"; import { monitor, monitorsToPages, + RegionEnum, selectMonitorSchema, } from "@openstatus/db/src/schema"; import { availableRegions } from "@openstatus/tinybird"; @@ -14,6 +15,9 @@ import type { payloadSchema } from "../schema"; const periodicityAvailable = selectMonitorSchema.pick({ periodicity: true }); +// FIXME: do coerce in zod instead +const currentRegions = z.string().transform((val) => val.split(",")); + const DEFAULT_URL = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"; @@ -48,7 +52,8 @@ export const cron = async ({ .where(eq(monitorsToPages.monitorId, row.id)) .all(); - for (const region of availableRegions) { + const allMonitorsRegions = currentRegions.parse(row.regions); + if (allMonitorsRegions.length === 0) { const payload: z.infer = { workspaceId: String(row.workspaceId), monitorId: String(row.id), @@ -59,11 +64,27 @@ export const cron = async ({ // TODO: fetch + try - catch + retry once const result = c.publishJSON({ - url: `${DEFAULT_URL}/api/checker/regions/${region}`, + url: `${DEFAULT_URL}/api/checker/regions/random`, body: payload, - delay: Math.random() * 180, + delay: Math.random() * 90, }); allResult.push(result); + } else { + for (const region of allMonitorsRegions) { + const payload: z.infer = { + workspaceId: String(row.workspaceId), + monitorId: String(row.id), + url: row.url, + cronTimestamp: timestamp, + pageIds: allPages.map((p) => String(p.pageId)), + }; + + const result = c.publishJSON({ + url: `${DEFAULT_URL}/api/checker/regions/${region}`, + body: payload, + }); + allResult.push(result); + } } } // our first legacy monitor @@ -82,7 +103,7 @@ export const cron = async ({ const result = c.publishJSON({ url: `${DEFAULT_URL}/api/checker/regions/${region}`, body: payload, - delay: Math.random() * 180, + delay: Math.random() * 90, }); allResult.push(result); } diff --git a/apps/web/src/app/api/checker/regions/_checker.ts b/apps/web/src/app/api/checker/regions/_checker.ts index 679c30c7..8529ee96 100644 --- a/apps/web/src/app/api/checker/regions/_checker.ts +++ b/apps/web/src/app/api/checker/regions/_checker.ts @@ -86,7 +86,7 @@ export const checker = async (request: Request, region: string) => { await monitor(res, result.data, region, latency); } catch (e) { // if on the third retry we still get an error, we should report it - if (request.headers.get("Upstash-Retried") === "3") { + if (request.headers.get("Upstash-Retried") === "2") { await monitor( { status: 500, text: () => Promise.resolve(`${e}`) }, result.data, diff --git a/apps/web/src/app/api/checker/regions/random/route.ts b/apps/web/src/app/api/checker/regions/random/route.ts new file mode 100644 index 00000000..bf05a27b --- /dev/null +++ b/apps/web/src/app/api/checker/regions/random/route.ts @@ -0,0 +1,17 @@ +import { NextResponse } from "next/server"; + +import { checker } from "../_checker"; + +export const runtime = "edge"; +export const preferredRegion = "auto"; +export const dynamic = "force-dynamic"; +// Fix is a random region let's figure where does vercel push it + +export async function POST(request: Request) { + const region = process.env.VERCEL_REGION; + if (!region) { + throw new Error("No region"); + } + await checker(request, region); + return NextResponse.json({ success: true }); +} diff --git a/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/_components/action-button.tsx b/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/_components/action-button.tsx index ead47dd0..9f8cd737 100644 --- a/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/_components/action-button.tsx +++ b/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/_components/action-button.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import Link from "next/link"; -import { usePathname, useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import { MoreVertical } from "lucide-react"; import type * as z from "zod"; diff --git a/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/page.tsx b/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/page.tsx index ec9b7ce4..963d60c2 100644 --- a/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/page.tsx +++ b/apps/web/src/app/app/(dashboard)/[workspaceSlug]/monitors/page.tsx @@ -57,7 +57,7 @@ export default async function MonitorPage({
-
Periodicity
+
Frequency
{monitor.periodicity}
diff --git a/apps/web/src/app/app/(dashboard)/[workspaceSlug]/status-pages/page.tsx b/apps/web/src/app/app/(dashboard)/[workspaceSlug]/status-pages/page.tsx index 56a244c7..c95c8a1c 100644 --- a/apps/web/src/app/app/(dashboard)/[workspaceSlug]/status-pages/page.tsx +++ b/apps/web/src/app/app/(dashboard)/[workspaceSlug]/status-pages/page.tsx @@ -14,9 +14,6 @@ import { EmptyState } from "./_components/empty-state"; const limit = allPlans.free.limits["status-pages"]; -// export const revalidate = 300; // revalidate this page every 5 minutes -export const dynamic = "force-dynamic"; - export default async function Page({ params, }: { diff --git a/apps/web/src/components/forms/montitor-form.tsx b/apps/web/src/components/forms/montitor-form.tsx index f6093b71..7c0d0beb 100644 --- a/apps/web/src/components/forms/montitor-form.tsx +++ b/apps/web/src/components/forms/montitor-form.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { zodResolver } from "@hookform/resolvers/zod"; +import { Check, ChevronsUpDown } from "lucide-react"; import { useForm } from "react-hook-form"; import type * as z from "zod"; @@ -11,6 +12,14 @@ import { } from "@openstatus/db/src/schema"; import { allPlans } from "@openstatus/plans"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; import { Form, FormControl, @@ -21,6 +30,11 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Select, SelectContent, @@ -29,6 +43,8 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { regionsDict } from "@/data/regions-dictionary"; +import { cn } from "@/lib/utils"; const limit = allPlans.free.limits.periodicity; const cronJobs = [ @@ -39,16 +55,14 @@ const cronJobs = [ { value: "1h", label: "1 hour" }, ] as const; -type Schema = z.infer; - interface Props { id: string; - defaultValues?: Schema; - onSubmit: (values: Schema) => Promise; + defaultValues?: z.infer; + onSubmit: (values: z.infer) => Promise; } export function MonitorForm({ id, defaultValues, onSubmit }: Props) { - const form = useForm({ + const form = useForm>({ resolver: zodResolver(insertMonitorSchema), // too much - we should only validate the values we ask inside of the form! defaultValues: { url: defaultValues?.url || "", @@ -57,6 +71,7 @@ export function MonitorForm({ id, defaultValues, onSubmit }: Props) { periodicity: defaultValues?.periodicity || undefined, active: defaultValues?.active || true, id: defaultValues?.id || undefined, + regions: defaultValues?.regions || [], }, }); @@ -134,12 +149,78 @@ export function MonitorForm({ id, defaultValues, onSubmit }: Props) { )} /> + ( + + Regions + + + + + + + + + + No regions found. + + {Object.keys(regionsDict).map((region) => { + const { code, location } = + regionsDict[region as keyof typeof regionsDict]; + const isSelected = field.value?.includes(code); + return ( + { + form.setValue("regions", [code]); // TODO: allow more than one to be selected in the future + }} + > + + {location} + + ); + })} + + + + + + Select the regions you want to monitor, or leave it blank for + randomly picked regions. + + + + )} + /> ( - Periodicity + Frequency