diff --git a/apps/server/src/v1/monitors/results/get.ts b/apps/server/src/v1/monitors/results/get.ts index 2efb42026f..099f3d7ae8 100644 --- a/apps/server/src/v1/monitors/results/get.ts +++ b/apps/server/src/v1/monitors/results/get.ts @@ -11,7 +11,7 @@ import { openApiErrorResponses } from "../../../libs/errors/openapi-error-respon import type { monitorsApi } from "../index"; import { ParamsSchema, ResultRun } from "../schema"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); +const tb = new OSTinybird(env.TINY_BIRD_API_KEY); const getMonitorStats = createRoute({ method: "get", @@ -69,15 +69,15 @@ export function registerGetMonitorResult(api: typeof monitorsApi) { throw new HTTPException(404, { message: "Not Found" }); } // Fetch result from tb pipe - const data = await tb.getResultForOnDemandCheckHttp()({ + const data = await tb.getResultForOnDemandCheckHttp({ monitorId: _monitor.id, timestamp: _monitorRun.runnedAt?.getTime(), url: _monitor.url, }); // return array of results - if (!data) { + if (!data || data.data.length === 0) { throw new HTTPException(404, { message: "Not Found" }); } - return c.json(data, 200); + return c.json(data.data, 200); }); } diff --git a/apps/server/src/v1/monitors/summary/get.ts b/apps/server/src/v1/monitors/summary/get.ts index 5abceefed1..134c53413c 100644 --- a/apps/server/src/v1/monitors/summary/get.ts +++ b/apps/server/src/v1/monitors/summary/get.ts @@ -12,7 +12,7 @@ import { isoDate } from "../../utils"; import type { monitorsApi } from "../index"; import { ParamsSchema } from "../schema"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); +const tb = new OSTinybird(env.TINY_BIRD_API_KEY); const redis = Redis.fromEnv(); const dailyStatsSchema = z.object({ @@ -82,17 +82,14 @@ export function registerGetMonitorSummary(api: typeof monitorsApi) { return c.json({ data: cache }, 200); } - // FIXME: we should use the OSTinybird client console.log("fetching from tinybird"); - const res = await tb.endpointStatusPeriod("45d")({ - monitorId: id, - }); + const res = await tb.httpStatus45d({ monitorId: id }); - if (res === undefined) { + if (!res || res.data.length === 0) { throw new HTTPException(404, { message: "Not Found" }); } await redis.set(`${id}-daily-stats`, res, { ex: 600 }); - return c.json({ data: res }, 200); + return c.json({ data: res.data }, 200); }); } diff --git a/apps/web/src/app/(content)/features/_components/tracker-example.tsx b/apps/web/src/app/(content)/features/_components/tracker-example.tsx index 08bf5fad22..8410dfe2aa 100644 --- a/apps/web/src/app/(content)/features/_components/tracker-example.tsx +++ b/apps/web/src/app/(content)/features/_components/tracker-example.tsx @@ -8,7 +8,7 @@ import { mockTrackerData } from "../mock"; export function TrackerWithVisibilityToggle() { const [visible, setVisible] = useState(true); return ( -
+
-

+

Share the uptime and number of requests.

diff --git a/apps/web/src/app/(content)/features/monitoring/page.tsx b/apps/web/src/app/(content)/features/monitoring/page.tsx index e26d848795..277d3cc1b7 100644 --- a/apps/web/src/app/(content)/features/monitoring/page.tsx +++ b/apps/web/src/app/(content)/features/monitoring/page.tsx @@ -8,8 +8,7 @@ import { Chart } from "@/components/monitor-charts/chart"; import { RegionsPreset } from "@/components/monitor-dashboard/region-preset"; import { ResponseDetailTabs } from "@/components/ping-response-analysis/response-detail-tabs"; import { marketingProductPagesConfig } from "@/config/pages"; -import { flyRegions } from "@openstatus/db/src/schema/constants"; -import type { Region } from "@openstatus/tinybird"; +import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; import { Button } from "@openstatus/ui/src/components/button"; import { Skeleton } from "@openstatus/ui/src/components/skeleton"; import { allUnrelateds } from "contentlayer/generated"; diff --git a/apps/web/src/app/_components/event-table.tsx b/apps/web/src/app/_components/event-table.tsx deleted file mode 100644 index 47217070ec..0000000000 --- a/apps/web/src/app/_components/event-table.tsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client"; - -import { formatDistance } from "date-fns"; -import React from "react"; - -import type { Ping } from "@openstatus/tinybird"; -import { - Badge, - Button, - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@openstatus/ui"; - -import { cn } from "@/lib/utils"; - -export function EventTable({ events }: { events: Ping[] }) { - const [open, toggle] = React.useReducer((state) => !state, false); - return ( -
-
- - - A list of the latest {events.length} pings. - - - - Time - Status - Latency (ms) - Region - - - - {events.map((event) => { - const isOk = event.statusCode === 200; - return ( - - - {formatDistance(new Date(event.timestamp), new Date(), { - addSuffix: true, - includeSeconds: true, - })} - - - - {event.statusCode} -
- - - - {event.latency} - - - {event.region} - - - ); - })} - -
-
- {!open && ( -
- -
- )} -
- ); -} diff --git a/apps/web/src/app/_components/input-search.tsx b/apps/web/src/app/_components/input-search.tsx deleted file mode 100644 index e758ca8823..0000000000 --- a/apps/web/src/app/_components/input-search.tsx +++ /dev/null @@ -1,200 +0,0 @@ -/// - -"use client"; - -import { Command as CommandPrimitive, useCommandState } from "cmdk"; -import React, { useEffect, useMemo, useRef, useState } from "react"; - -import type { Ping } from "@openstatus/tinybird"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandItem, - CommandList, -} from "@openstatus/ui"; - -// TODO: once stable, use the shallow route to store the search params inside of the search params - -export function InputSearch({ - events, - onSearch, -}: { - onSearch(value: Record): void; - events: Ping[]; -}) { - const inputRef = useRef(null); - const [open, setOpen] = useState(false); - const [inputValue, setInputValue] = useState(""); - const [currentWord, setCurrentWord] = useState(""); - - // TODO: check if there is a move efficient way - useEffect(() => { - const searchparams = inputValue - .trim() - .split(" ") - .reduce( - (prev, curr) => { - const [name, value] = curr.split(":"); - if (value && name && curr !== currentWord) { - // TODO: support multiple value with value.split(",") - prev[name] = value; - } - return prev; - }, - {} as Record, - ); - onSearch(searchparams); - }, [onSearch, inputValue, currentWord]); - - // DEFINE YOUR SEARCH PARAMETERS - const search = useMemo( - () => - events.reduce( - (prev, curr) => { - return { - // biome-ignore lint/performance/noAccumulatingSpread: - ...prev, - status: [...new Set([curr.statusCode, ...(prev.status || [])])], - region: [...new Set([curr.region, ...(prev.region || [])])], - }; - }, - // defaultState - { limit: [10, 25, 50], status: [], region: [] } as { - status: (number | null)[]; - limit: number[]; - region: string[]; - }, - ), - [events], - ); - - type SearchKey = keyof typeof search; - - return ( - { - if (value.includes(currentWord.toLowerCase())) return 1; - return 0; - }} - > - { - if (e.key === "Escape") inputRef?.current?.blur(); - }} - onBlur={() => setOpen(false)} - onFocus={() => setOpen(true)} - onInput={(e) => { - const caretPositionStart = e.currentTarget?.selectionStart || -1; - const inputValue = e.currentTarget?.value || ""; - - let start = caretPositionStart; - let end = caretPositionStart; - - while (start > 0 && inputValue[start - 1] !== " ") { - start--; - } - while (end < inputValue.length && inputValue[end] !== " ") { - end++; - } - - const word = inputValue.substring(start, end); - setCurrentWord(word); - }} - placeholder={`${events.length} total logs found...`} - className="flex-1 rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none ring-offset-background placeholder:text-muted-foreground focus:ring-2 focus:ring-ring focus:ring-offset-2" - /> -
- {open ? ( -
- - - {Object.keys(search).map((key) => { - if ( - inputValue.includes(`${key}:`) && - !currentWord.includes(`${key}:`) - ) - return null; - return ( - - { - e.preventDefault(); - e.stopPropagation(); - }} - onSelect={(value) => { - setInputValue((prev) => { - if (currentWord.trim() === "") { - const input = `${prev}${value}`; - return `${input}:`; - } - // lots of cheat - const isStarting = currentWord === prev; - const prefix = isStarting ? "" : " "; - const input = prev.replace( - `${prefix}${currentWord}`, - `${prefix}${value}`, - ); - return `${input}:`; - }); - setCurrentWord(`${value}:`); - }} - className="group" - > - {key} - - {search[key as SearchKey] - .map((str) => `[${str}]`) - .join(" ")} - - - {search[key as SearchKey].map((option) => { - return ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onSelect={(value) => { - setInputValue((prev) => { - const input = prev.replace(currentWord, value); - return `${input.trim()} `; - }); - setCurrentWord(""); - }} - {...{ currentWord }} - > - {option} - - ); - })} - - ); - })} - - No results found. - -
- ) : null} -
-
- ); -} - -interface SubItemProps - extends React.ComponentPropsWithoutRef { - currentWord: string; -} - -const SubItem = ({ currentWord, ...props }: SubItemProps) => { - const search = useCommandState((state) => state.search); - if (!search.includes(":") || !currentWord.includes(":")) return null; - return ; -}; diff --git a/apps/web/src/app/_components/table-input-container.tsx b/apps/web/src/app/_components/table-input-container.tsx deleted file mode 100644 index 7e3e1c7d33..0000000000 --- a/apps/web/src/app/_components/table-input-container.tsx +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; - -import React from "react"; - -import type { Ping } from "@openstatus/tinybird"; - -import { EventTable } from "./event-table"; -import { InputSearch } from "./input-search"; - -// TODO: once stable, use the shallow route to store the search params inside of the search params - -export function TableInputContainer({ events }: { events: Ping[] }) { - const [search, setSearch] = React.useState>({}); - - const filteredEvents = events - .filter((event) => { - if (search?.status && event.statusCode !== Number(search.status)) { - return false; - } - if (search?.region && event.region !== search.region) { - return false; - } - return true; - }) - .slice(0, search.limit ? Number(search.limit) : 100); - - return ( - <> - - - - ); -} diff --git a/apps/web/src/app/api/checker/test/route.ts b/apps/web/src/app/api/checker/test/route.ts deleted file mode 100644 index d5fc8d4052..0000000000 --- a/apps/web/src/app/api/checker/test/route.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NextResponse } from "next/server"; -import { z } from "zod"; - -import { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; - -import { checkRegion } from "@/components/ping-response-analysis/utils"; -import { httpPayloadSchema } from "@openstatus/utils"; -import { isAnInvalidTestUrl } from "../utils"; - -export const runtime = "edge"; -export const preferredRegion = "auto"; -export const dynamic = "force-dynamic"; -export const revalidate = 0; - -export function GET() { - return NextResponse.json({ success: true }); -} - -export async function POST(request: Request) { - try { - const json = await request.json(); - const _valid = httpPayloadSchema - .pick({ url: true, method: true, headers: true, body: true }) - .merge(z.object({ region: monitorFlyRegionSchema.default("ams") })) - .safeParse(json); - - if (!_valid.success) { - return NextResponse.json({ success: false }, { status: 400 }); - } - - const { url, region, method, headers, body } = _valid.data; - // 🧑‍💻 for the smart one who want to create a loop hole - if (isAnInvalidTestUrl(url)) { - return NextResponse.json({ success: true }, { status: 200 }); - } - - const res = await checkRegion(url, region, { method, headers, body }); - - return NextResponse.json(res); - } catch (e) { - console.error(e); - return NextResponse.json({ success: false }, { status: 400 }); - } -} diff --git a/apps/web/src/app/api/checker/test/tcp/route.ts b/apps/web/src/app/api/checker/test/tcp/route.ts index df8abd2990..56d7004e2d 100644 --- a/apps/web/src/app/api/checker/test/tcp/route.ts +++ b/apps/web/src/app/api/checker/test/tcp/route.ts @@ -21,6 +21,7 @@ export async function POST(request: Request) { try { const json = await request.json(); const _valid = tcpPayload + .pick({ url: true }) .merge(z.object({ region: monitorFlyRegionSchema.default("ams") })) .safeParse(json); @@ -40,7 +41,7 @@ export async function POST(request: Request) { } async function checkTCP(url: string, region: MonitorFlyRegion) { // - const res = await fetch(`https://checker.openstatus.dev/ping/tcp/${region}`, { + const res = await fetch(`https://checker.openstatus.dev/tcp/${region}`, { headers: { Authorization: `Basic ${process.env.CRON_SECRET}`, "Content-Type": "application/json", @@ -58,7 +59,6 @@ async function checkTCP(url: string, region: MonitorFlyRegion) { const data = TCPResponse.safeParse(json); if (!data.success) { - console.log(json); console.error( `something went wrong with result ${json} request to ${url} error ${data.error.message}`, ); diff --git a/apps/web/src/app/api/checker/test/tcp/schema.ts b/apps/web/src/app/api/checker/test/tcp/schema.ts index af234693ac..f1b52fdf9e 100644 --- a/apps/web/src/app/api/checker/test/tcp/schema.ts +++ b/apps/web/src/app/api/checker/test/tcp/schema.ts @@ -12,9 +12,9 @@ export const tcpPayload = z.object({ }); export const TCPResponse = z.object({ - requestId: z.string().optional(), - workspaceId: z.string(), - monitorId: z.string(), + requestId: z.number().optional(), + workspaceId: z.number().optional(), + monitorId: z.number().optional(), timestamp: z.number(), timing: z.object({ tcpStart: z.number(), diff --git a/apps/web/src/app/api/og/_components/tracker.tsx b/apps/web/src/app/api/og/_components/tracker.tsx index 9d3cfc86f4..952dbdfb3c 100644 --- a/apps/web/src/app/api/og/_components/tracker.tsx +++ b/apps/web/src/app/api/og/_components/tracker.tsx @@ -1,9 +1,13 @@ -import type { Monitor } from "@openstatus/tinybird"; import { Tracker as OSTracker, classNames } from "@openstatus/tracker"; +import type { ResponseStatusTracker } from "@/lib/tb"; import { cn, formatDate } from "@/lib/utils"; -export function Tracker({ data }: { data: Monitor[] }) { +interface TrackerProps { + data: ResponseStatusTracker[]; +} + +export function Tracker({ data }: TrackerProps) { const tracker = new OSTracker({ data }); return ( diff --git a/apps/web/src/app/api/og/monitor/route.tsx b/apps/web/src/app/api/og/monitor/route.tsx index e15facbfab..d4be6f6ada 100644 --- a/apps/web/src/app/api/og/monitor/route.tsx +++ b/apps/web/src/app/api/og/monitor/route.tsx @@ -8,7 +8,7 @@ import { BasicLayout } from "../_components/basic-layout"; import { Tracker } from "../_components/tracker"; import { SIZE, calSemiBold, interLight, interRegular } from "../utils"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); +const tb = new OSTinybird(env.TINY_BIRD_API_KEY); export const runtime = "edge"; @@ -29,20 +29,20 @@ export async function GET(req: Request) { const monitorId = (searchParams.has("id") && searchParams.get("id")) || undefined; - const data = - (monitorId && - (await tb.endpointStatusPeriod("45d")({ - monitorId, - }))) || - []; + // TODO: we need to pass the monitor type here + + const res = (monitorId && + (await tb.httpStatus45d({ + monitorId, + }))) || { data: [] }; return new ImageResponse( - {data.length ? : null} + {res.data.length ? : null} , { ...SIZE, diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/(overview)/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/(overview)/page.tsx index 857c2d4cef..e957593749 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/(overview)/page.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/(overview)/page.tsx @@ -1,18 +1,15 @@ import Link from "next/link"; -import { OSTinybird } from "@openstatus/tinybird"; import { Button } from "@openstatus/ui/src/components/button"; import { EmptyState } from "@/components/dashboard/empty-state"; import { Limit } from "@/components/dashboard/limit"; import { columns } from "@/components/data-table/monitor/columns"; import { DataTable } from "@/components/data-table/monitor/data-table"; -import { env } from "@/env"; +import { prepareMetricsByPeriod, prepareStatusByPeriod } from "@/lib/tb"; import { api } from "@/trpc/server"; import { searchParamsCache } from "./search-params"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export default async function MonitorPage({ searchParams, }: { @@ -46,22 +43,17 @@ export default async function MonitorPage({ // use Suspense and Client call instead? const monitorsWithData = await Promise.all( monitors.map(async (monitor) => { + const type = monitor.jobType as "http" | "tcp"; const [metrics, data] = await Promise.all([ - tb.endpointMetrics("1d")( - { - monitorId: String(monitor.id), - }, - { cache: "no-store", revalidate: 0 }, - ), - tb.endpointStatusPeriod("7d")( - { - monitorId: String(monitor.id), - }, - { cache: "no-store", revalidate: 0 }, - ), + prepareMetricsByPeriod("1d", type).getData({ + monitorId: String(monitor.id), + }), + prepareStatusByPeriod("7d", type).getData({ + monitorId: String(monitor.id), + }), ]); - const [current] = metrics?.sort((a, b) => + const [current] = metrics.data?.sort((a, b) => (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1, ) || [undefined]; @@ -80,7 +72,7 @@ export default async function MonitorPage({ return { monitor, metrics: current, - data, + data: data.data, incidents, maintenances, tags, diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/_components/data-table-wrapper.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/_components/data-table-wrapper.tsx index 0bfeb41a8d..a1f590bda5 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/_components/data-table-wrapper.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/_components/data-table-wrapper.tsx @@ -11,7 +11,6 @@ import type { import { Suspense, use } from "react"; import * as assertions from "@openstatus/assertions"; -import type { OSTinybird } from "@openstatus/tinybird"; import { CopyToClipboardButton } from "@/components/dashboard/copy-to-clipboard-button"; import { columns } from "@/components/data-table/columns"; @@ -23,22 +22,17 @@ import { api } from "@/trpc/client"; import type { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; import type { z } from "zod"; -// EXAMPLE: get the type of the response of the endpoint -// biome-ignore lint/correctness/noUnusedVariables: -type T = Awaited>>; - // FIXME: use proper type export type Monitor = { + type: "http" | "tcp"; monitorId: string; - url: string; latency: number; region: z.infer; - statusCode: number | null; + statusCode?: number | null; timestamp: number; workspaceId: string; cronTimestamp: number | null; error: boolean; - assertions?: string | null; trigger: Trigger | null; }; @@ -55,10 +49,14 @@ export function DataTableWrapper({ true} + // REMINDER: we currently only support HTTP monitors with more details + getRowCanExpand={(row) => row.original.type === "http"} renderSubComponent={renderSubComponent} defaultColumnFilters={filters} defaultPagination={pagination} + defaultVisibility={ + data.length && data[0].type === "tcp" ? { statusCode: false } : {} + } /> ); } @@ -77,26 +75,25 @@ function renderSubComponent({ row }: { row: Row }) { ); } +// REMINDER: only HTTP monitors have more details function Details({ row }: { row: Row }) { const data = use( - api.tinybird.responseDetails.query({ + api.tinybird.httpGetMonthly.query({ monitorId: row.original.monitorId, - url: row.original.url, region: row.original.region, cronTimestamp: row.original.cronTimestamp || undefined, }), ); - if (!data || data.length === 0) return

Something went wrong

; + if (!data.data || data.data.length === 0) return

Something went wrong

; - const first = data?.[0]; + const first = data.data?.[0]; // FIXME: ugly hack const url = new URL(window.location.href.replace("/data", "/details")); url.searchParams.set("monitorId", row.original.monitorId); url.searchParams.set("region", row.original.region); url.searchParams.set("cronTimestamp", String(row.original.cronTimestamp)); - url.searchParams.set("url", row.original.url); return (
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/page.tsx index 0296f3f4ed..5eec617250 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/page.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/page.tsx @@ -1,16 +1,12 @@ import { notFound } from "next/navigation"; import * as React from "react"; -import { OSTinybird } from "@openstatus/tinybird"; - import { DatePickerPreset } from "@/components/monitor-dashboard/date-picker-preset"; -import { env } from "@/env"; +import { prepareListByPeriod } from "@/lib/tb"; import { api } from "@/trpc/server"; import { DataTableWrapper } from "./_components/data-table-wrapper"; import { searchParamsCache } from "./search-params"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export default async function Page({ params, searchParams, @@ -27,12 +23,17 @@ export default async function Page({ if (!monitor) return notFound(); - const allowedPeriods = ["1h", "1d", "3d", "7d"] as const; + const type = monitor.jobType as "http" | "tcp"; + + // FIXME: make it dynamic based on the workspace plan + const allowedPeriods = ["1d", "7d", "14d"] as const; const period = allowedPeriods.find((i) => i === search.period) || "1d"; - const data = await tb.endpointList(period)({ monitorId: id }); + const res = await prepareListByPeriod(period, type).getData({ + monitorId: id, + }); - if (!data) return null; + if (!res.data || res.data.length === 0) return null; return (
@@ -47,7 +48,7 @@ export default async function Page({
{/* FIXME: we display all the regions even though a user might not have all supported in their plan */} ; + return ; } catch (_e) { return ; } diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx index ef2375e970..bbd4718d22 100644 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx +++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx @@ -1,18 +1,20 @@ import { notFound } from "next/navigation"; import * as React from "react"; -import { flyRegions } from "@openstatus/db/src/schema/constants"; -import type { Region } from "@openstatus/tinybird"; -import { OSTinybird } from "@openstatus/tinybird"; +import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; import { Separator } from "@openstatus/ui"; import { CombinedChartWrapper } from "@/components/monitor-charts/combined-chart-wrapper"; import { ButtonReset } from "@/components/monitor-dashboard/button-reset"; import { DatePickerPreset } from "@/components/monitor-dashboard/date-picker-preset"; import { Metrics } from "@/components/monitor-dashboard/metrics"; -import { env } from "@/env"; import { getMinutesByInterval, periods } from "@/lib/monitor/utils"; import { getPreferredSettings } from "@/lib/preferred-settings/server"; +import { + prepareMetricByIntervalByPeriod, + prepareMetricByRegionByPeriod, + prepareMetricsByPeriod, +} from "@/lib/tb"; import { api } from "@/trpc/server"; import { DEFAULT_INTERVAL, @@ -21,8 +23,6 @@ import { searchParamsCache, } from "./search-params"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export default async function Page({ params, searchParams, @@ -42,6 +42,7 @@ export default async function Page({ if (!monitor) return notFound(); const { period, quantile, interval, regions } = search; + const type = monitor.jobType as "http" | "tcp"; // TODO: work it out easier const intervalMinutes = getMinutesByInterval(interval); @@ -51,12 +52,14 @@ export default async function Page({ const minutes = isQuantileDisabled ? periodicityMinutes : intervalMinutes; const [metrics, data, metricsByRegion] = await Promise.all([ - tb.endpointMetrics(period)({ monitorId: id }), - tb.endpointChart(period)({ + prepareMetricsByPeriod(period, type).getData({ + monitorId: id, + }), + prepareMetricByIntervalByPeriod(period, type).getData({ monitorId: id, interval: minutes, }), - tb.endpointMetricsByRegion(period)({ + prepareMetricByRegionByPeriod(period, type).getData({ monitorId: id, }), ]); @@ -84,17 +87,17 @@ export default async function Page({ {isDirty ? : null}
- +
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/_components/refresh-widget.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/_components/refresh-widget.tsx deleted file mode 100644 index 7e05805d3d..0000000000 --- a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/_components/refresh-widget.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; - -import { Button } from "@openstatus/ui/src/components/button"; - -import { api } from "@/trpc/client"; - -// TODO: instead of setInterval, use staleWhileRevalidate method to fetch latest data -// also fetch data on page focus - -export function RefreshWidget({ defaultValue }: { defaultValue?: number }) { - const [refresh, setRefresh] = useState(false); - const [value, setValue] = useState(defaultValue); - const router = useRouter(); - - useEffect(() => { - const intervalId = setInterval(async () => { - if (refresh) return; - const data = await api.tinybird.lastCronTimestamp.query(); - if (data && data?.length > 0) { - const { cronTimestamp } = data[0]; - setValue(cronTimestamp); - if (value && cronTimestamp > value) setRefresh(true); - } - }, 30_000); - return () => { - clearInterval(intervalId); - }; - }, [refresh, value]); - - if (!refresh) return null; - - return ( -
- -
- ); -} diff --git a/apps/web/src/app/play/checker/_components/checker-form.tsx b/apps/web/src/app/play/checker/_components/checker-form.tsx index 396d8003fa..b750c2e8b7 100644 --- a/apps/web/src/app/play/checker/_components/checker-form.tsx +++ b/apps/web/src/app/play/checker/_components/checker-form.tsx @@ -7,9 +7,6 @@ import { useForm } from "react-hook-form"; import * as z from "zod"; import { - Alert, - AlertDescription, - AlertTitle, Button, Checkbox, Form, @@ -27,7 +24,6 @@ import { SelectValue, Table, TableBody, - TableCaption, TableCell, TableHead, TableHeader, @@ -253,13 +249,13 @@ export function CheckerForm({ defaultValues, defaultData }: CheckerFormProps) { )} />
- diff --git a/apps/web/src/app/play/checker/api/mock.ts b/apps/web/src/app/play/checker/api/mock.ts index 3e5950cbda..990d87d51c 100644 --- a/apps/web/src/app/play/checker/api/mock.ts +++ b/apps/web/src/app/play/checker/api/mock.ts @@ -1,6 +1,6 @@ import type { RegionChecker } from "@/components/ping-response-analysis/utils"; import { wait } from "@/lib/utils"; -import type { Region } from "@openstatus/tinybird"; +import type { Region } from "@openstatus/db/src/schema/constants"; export async function mockCheckRegion(region: Region) { const response = data.checks.find((check) => check.region === region); diff --git a/apps/web/src/app/play/status/_components/status-play.tsx b/apps/web/src/app/play/status/_components/status-play.tsx index 6cbbd81d13..3142dd3777 100644 --- a/apps/web/src/app/play/status/_components/status-play.tsx +++ b/apps/web/src/app/play/status/_components/status-play.tsx @@ -1,5 +1,3 @@ -import { OSTinybird } from "@openstatus/tinybird"; - import { CardContainer, CardDescription, @@ -9,20 +7,11 @@ import { } from "@/components/marketing/card"; import { Tracker } from "@/components/tracker/tracker"; import { env } from "@/env"; +import { prepareStatusByPeriod } from "@/lib/tb"; import { getServerTimezoneFormat } from "@/lib/timezone"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export default async function StatusPlay() { - const data = await tb.endpointStatusPeriod("45d")( - { - monitorId: "1", - }, - { - revalidate: 600, // 10 minutes - }, - ); - + const res = await prepareStatusByPeriod("45d").getData({ monitorId: "1" }); const formattedServerDate = getServerTimezoneFormat(); return ( @@ -37,7 +26,9 @@ export default async function StatusPlay() {
- {data && } + {res.data && ( + + )}

{formattedServerDate} diff --git a/apps/web/src/app/public/monitors/[id]/page.tsx b/apps/web/src/app/public/monitors/[id]/page.tsx index 84ae2b9c0e..a30f13566f 100644 --- a/apps/web/src/app/public/monitors/[id]/page.tsx +++ b/apps/web/src/app/public/monitors/[id]/page.tsx @@ -1,9 +1,7 @@ import { notFound } from "next/navigation"; import * as React from "react"; -import { flyRegions } from "@openstatus/db/src/schema/constants"; -import type { Region } from "@openstatus/tinybird"; -import { OSTinybird } from "@openstatus/tinybird"; +import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; import { Separator } from "@openstatus/ui"; import { Shell } from "@/components/dashboard/shell"; @@ -11,9 +9,13 @@ import { CombinedChartWrapper } from "@/components/monitor-charts/combined-chart import { ButtonReset } from "@/components/monitor-dashboard/button-reset"; import { DatePickerPreset } from "@/components/monitor-dashboard/date-picker-preset"; import { Metrics } from "@/components/monitor-dashboard/metrics"; -import { env } from "@/env"; import { getMinutesByInterval } from "@/lib/monitor/utils"; import { getPreferredSettings } from "@/lib/preferred-settings/server"; +import { + prepareMetricByIntervalByPeriod, + prepareMetricByRegionByPeriod, + prepareMetricsByPeriod, +} from "@/lib/tb"; import { api } from "@/trpc/server"; import { DEFAULT_INTERVAL, @@ -23,8 +25,6 @@ import { searchParamsCache, } from "./search-params"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export default async function Page({ params, searchParams, @@ -43,6 +43,7 @@ export default async function Page({ if (!monitor) return notFound(); const { period, quantile, interval, regions } = search; + const type = monitor.jobType as "http" | "tcp"; // TODO: work it out easier const intervalMinutes = getMinutesByInterval(interval); @@ -52,12 +53,14 @@ export default async function Page({ const minutes = isQuantileDisabled ? periodicityMinutes : intervalMinutes; const [metrics, data, metricsByRegion] = await Promise.all([ - tb.endpointMetrics(period)({ monitorId: id }), - await tb.endpointChart(period)({ + prepareMetricsByPeriod(period, type).getData({ + monitorId: id, + }), + prepareMetricByIntervalByPeriod(period, type).getData({ monitorId: id, interval: minutes, }), - tb.endpointMetricsByRegion(period)({ + prepareMetricByRegionByPeriod(period, type).getData({ monitorId: id, }), ]); @@ -90,17 +93,17 @@ export default async function Page({

- + diff --git a/apps/web/src/app/status-page/[domain]/monitors/[id]/page.tsx b/apps/web/src/app/status-page/[domain]/monitors/[id]/page.tsx index 1c7f353c8f..78a2fd352e 100644 --- a/apps/web/src/app/status-page/[domain]/monitors/[id]/page.tsx +++ b/apps/web/src/app/status-page/[domain]/monitors/[id]/page.tsx @@ -1,9 +1,7 @@ import { notFound } from "next/navigation"; import * as React from "react"; -import { flyRegions } from "@openstatus/db/src/schema/constants"; -import type { Region } from "@openstatus/tinybird"; -import { OSTinybird } from "@openstatus/tinybird"; +import { type Region, flyRegions } from "@openstatus/db/src/schema/constants"; import { Separator } from "@openstatus/ui/src/components/separator"; import { Header } from "@/components/dashboard/header"; @@ -11,9 +9,13 @@ import { CombinedChartWrapper } from "@/components/monitor-charts/combined-chart import { ButtonReset } from "@/components/monitor-dashboard/button-reset"; import { DatePickerPreset } from "@/components/monitor-dashboard/date-picker-preset"; import { Metrics } from "@/components/monitor-dashboard/metrics"; -import { env } from "@/env"; import { getMinutesByInterval } from "@/lib/monitor/utils"; import { getPreferredSettings } from "@/lib/preferred-settings/server"; +import { + prepareMetricByIntervalByPeriod, + prepareMetricByRegionByPeriod, + prepareMetricsByPeriod, +} from "@/lib/tb"; import { api } from "@/trpc/server"; import { DEFAULT_INTERVAL, @@ -23,8 +25,6 @@ import { searchParamsCache, } from "./search-params"; -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export const revalidate = 120; export default async function Page({ @@ -46,6 +46,7 @@ export default async function Page({ if (!monitor) return notFound(); const { period, quantile, interval, regions } = search; + const type = monitor.jobType as "http" | "tcp"; // TODO: work it out easier const intervalMinutes = getMinutesByInterval(interval); @@ -55,12 +56,12 @@ export default async function Page({ const minutes = isQuantileDisabled ? periodicityMinutes : intervalMinutes; const [metrics, data, metricsByRegion] = await Promise.all([ - tb.endpointMetrics(period)({ monitorId: id }), - tb.endpointChart(period)({ + prepareMetricsByPeriod(period, type).getData({ monitorId: id }), + prepareMetricByIntervalByPeriod(period, type).getData({ monitorId: id, interval: minutes, }), - tb.endpointMetricsByRegion(period)({ + prepareMetricByRegionByPeriod(period, type).getData({ monitorId: id, }), ]); @@ -73,6 +74,8 @@ export default async function Page({ interval !== DEFAULT_INTERVAL || flyRegions.length !== regions.length; + console.log({ metrics: metrics.data }); + return (
@@ -80,17 +83,17 @@ export default async function Page({ {isDirty ? : null}
- +
diff --git a/apps/web/src/app/status-page/[domain]/monitors/[id]/search-params.ts b/apps/web/src/app/status-page/[domain]/monitors/[id]/search-params.ts index d68907c872..a704e0d3d6 100644 --- a/apps/web/src/app/status-page/[domain]/monitors/[id]/search-params.ts +++ b/apps/web/src/app/status-page/[domain]/monitors/[id]/search-params.ts @@ -11,7 +11,7 @@ export const DEFAULT_QUANTILE = "p95"; export const DEFAULT_INTERVAL = "30m"; export const DEFAULT_PERIOD = "1d"; -export const periods = ["1d", "7d"] as const; +export const periods = ["1d", "7d", "14d"] as const; export const searchParamsParsers = { statusCode: parseAsInteger, diff --git a/apps/web/src/app/status-page/[domain]/monitors/page.tsx b/apps/web/src/app/status-page/[domain]/monitors/page.tsx index beb396ce6b..6cefa44655 100644 --- a/apps/web/src/app/status-page/[domain]/monitors/page.tsx +++ b/apps/web/src/app/status-page/[domain]/monitors/page.tsx @@ -2,21 +2,18 @@ import { ChevronRight } from "lucide-react"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { OSTinybird } from "@openstatus/tinybird"; import { Button } from "@openstatus/ui/src/components/button"; import { EmptyState } from "@/components/dashboard/empty-state"; import { Header } from "@/components/dashboard/header"; import { SimpleChart } from "@/components/monitor-charts/simple-chart"; import { groupDataByTimestamp } from "@/components/monitor-charts/utils"; -import { env } from "@/env"; +import { prepareMetricByIntervalByPeriod } from "@/lib/tb"; import { api } from "@/trpc/server"; import { searchParamsCache } from "./search-params"; // Add loading page -const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); - export const revalidate = 120; export default async function Page({ @@ -39,8 +36,13 @@ export default async function Page({ publicMonitors.length > 0 ? await Promise.all( publicMonitors?.map(async (monitor) => { - const data = await tb.endpointChartAllRegions(period)({ + const type = monitor.jobType as "http" | "tcp"; + const data = await prepareMetricByIntervalByPeriod( + period, + type, + ).getData({ monitorId: String(monitor.id), + interval: 60, }); return { monitor, data }; @@ -70,12 +72,7 @@ export default async function Page({