From cbb95f244f36d4d02113c9151d4717382cda916c Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 29 Aug 2024 17:23:41 +0100 Subject: [PATCH] Using ElectricSQL for the run page data --- apps/webapp/app/env.server.ts | 1 + apps/webapp/app/hooks/useTrace.ts | 26 ++++++++++++++++ .../route.tsx | 28 ++++++++++++----- .../webapp/app/routes/sync.traces.$traceId.ts | 31 +++++++++++++++++++ apps/webapp/package.json | 1 + pnpm-lock.yaml | 30 ++++++++++++++++++ 6 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 apps/webapp/app/hooks/useTrace.ts create mode 100644 apps/webapp/app/routes/sync.traces.$traceId.ts diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 911e3ece8..76a6b624d 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -31,6 +31,7 @@ const EnvironmentSchema = z.object({ REMIX_APP_PORT: z.string().optional(), LOGIN_ORIGIN: z.string().default("http://localhost:3030"), APP_ORIGIN: z.string().default("http://localhost:3030"), + ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"), APP_ENV: z.string().default(process.env.NODE_ENV), SERVICE_NAME: z.string().default("trigger.dev webapp"), SECRET_STORE: SecretStoreOptionsSchema.default("DATABASE"), diff --git a/apps/webapp/app/hooks/useTrace.ts b/apps/webapp/app/hooks/useTrace.ts new file mode 100644 index 000000000..9b7d6e3a8 --- /dev/null +++ b/apps/webapp/app/hooks/useTrace.ts @@ -0,0 +1,26 @@ +import { useShape } from "@electric-sql/react"; +import { createTraceTreeFromEvents, prepareTrace } from "~/utils/taskEvent"; +import { QueriedEvent } from "~/v3/eventRepository.server"; + +type TraceInput = { + origin: string; + traceId: string; + spanId: string; +}; + +export type Trace = ReturnType; +export type TraceEvent = NonNullable; + +export function useTrace({ origin, traceId, spanId }: TraceInput) { + const { isUpToDate, data } = useShape({ + url: `${origin}/sync/traces/${traceId}`, + }); + + const events = prepareTrace(data as QueriedEvent[]); + if (!events) { + return { isUpToDate, trace: undefined }; + } + + const trace = createTraceTreeFromEvents(events, spanId); + return { isUpToDate, trace }; +} diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx index 5c1641764..ddcb153b3 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam/route.tsx @@ -1,3 +1,4 @@ +import { useShape } from "@electric-sql/react"; import { ArrowUturnLeftIcon, BoltSlashIcon, @@ -80,8 +81,9 @@ import { } from "~/utils/pathBuilder"; import { SpanView } from "../resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route"; import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route"; - -type TraceEvent = NonNullable["trace"]>["events"][0]; +import { Trace, TraceEvent, useTrace } from "~/hooks/useTrace"; +import { trace } from "console"; +import { useAppOrigin } from "~/hooks/useAppOrigin"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); @@ -100,9 +102,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { return json({ run: result.run, - trace: result.trace, - maximumLiveReloadingSetting: env.MAXIMUM_LIVE_RELOADING_EVENTS, resizeSettings, + maximumLiveReloadingSetting: env.MAXIMUM_LIVE_RELOADING_EVENTS, }); }; @@ -114,8 +115,15 @@ function getSpanId(location: Location): string | undefined { } export default function Page() { - const { run, trace, resizeSettings, maximumLiveReloadingSetting } = - useLoaderData(); + const { run, resizeSettings, maximumLiveReloadingSetting } = useLoaderData(); + const appOrigin = useAppOrigin(); + const { location, replaceSearchParam } = useReplaceLocation(); + + const { isUpToDate, trace } = useTrace({ + origin: appOrigin, + traceId: run.traceId, + spanId: getSpanId(location) ?? run.spanId, + }); const user = useUser(); const organization = useOrganization(); const project = useProject(); @@ -223,7 +231,11 @@ export default function Page() { ); } -function TraceView({ run, trace, maximumLiveReloadingSetting, resizeSettings }: LoaderData) { +type TraceData = Pick & { + trace: Trace | undefined; +}; + +function TraceView({ run, trace, maximumLiveReloadingSetting, resizeSettings }: TraceData) { const organization = useOrganization(); const project = useProject(); const { location, replaceSearchParam } = useReplaceLocation(); @@ -301,7 +313,7 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizeSettings }: ); } -function NoLogsView({ run, resizeSettings }: LoaderData) { +function NoLogsView({ run, resizeSettings }: TraceData) { const plan = useCurrentPlan(); const organization = useOrganization(); diff --git a/apps/webapp/app/routes/sync.traces.$traceId.ts b/apps/webapp/app/routes/sync.traces.$traceId.ts new file mode 100644 index 000000000..c83fb60ff --- /dev/null +++ b/apps/webapp/app/routes/sync.traces.$traceId.ts @@ -0,0 +1,31 @@ +import type { LoaderFunctionArgs } from "@remix-run/node"; +import { env } from "~/env.server"; + +export async function loader({ params, request }: LoaderFunctionArgs) { + const url = new URL(request.url); + const { table } = params; + const originUrl = new URL(`${env.ELECTRIC_ORIGIN}/v1/shape/public."TaskEvent"`); + url.searchParams.forEach((value, key) => { + originUrl.searchParams.set(key, value); + }); + + originUrl.searchParams.set("where", `"traceId"='${params.traceId}'`); + + // When proxying long-polling requests, content-encoding & content-length are added + // erroneously (saying the body is gzipped when it's not) so we'll just remove + // them to avoid content decoding errors in the browser. + // + // Similar-ish problem to https://github.com/wintercg/fetch/issues/23 + let response = await fetch(originUrl.toString()); + if (response.headers.get(`content-encoding`)) { + const headers = new Headers(response.headers); + headers.delete(`content-encoding`); + headers.delete(`content-length`); + response = new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }); + } + return response; +} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 64efcde7d..6227c6ec1 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -46,6 +46,7 @@ "@conform-to/react": "^0.6.1", "@conform-to/zod": "^0.6.1", "@depot/sdk-node": "^0.5.0", + "@electric-sql/react": "^0.3.1", "@headlessui/react": "^1.7.8", "@heroicons/react": "^2.0.12", "@internationalized/date": "^3.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9089fdbd6..ad157ff0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -231,6 +231,9 @@ importers: '@depot/sdk-node': specifier: ^0.5.0 version: 0.5.0 + '@electric-sql/react': + specifier: ^0.3.1 + version: 0.3.1(react@18.2.0) '@headlessui/react': specifier: ^1.7.8 version: 1.7.8(react-dom@18.2.0)(react@18.2.0) @@ -4124,6 +4127,25 @@ packages: '@connectrpc/connect-node': 0.13.2(@bufbuild/protobuf@1.7.2) dev: false + /@electric-sql/client@0.3.2: + resolution: {integrity: sha512-DVOtrIfg7xlXpU95F53MnLXLeIn1eYkSRz01vj63NzZZ7bf6vLSHzNKvil0413C3U3769btfQytGNWmsb744WQ==} + optionalDependencies: + '@rollup/rollup-darwin-arm64': 4.21.1 + dev: false + + /@electric-sql/react@0.3.1(react@18.2.0): + resolution: {integrity: sha512-U+7ssfS+4UuGYXTyqGqXJGN04hrwjI8qKcv5l1br59EX93yfrFVVEfRsCQIe0mTiFTS/im9k+SUBIPrC/g8n2A==} + peerDependencies: + react: ^18.3.1 + peerDependenciesMeta: + react: + optional: true + dependencies: + '@electric-sql/client': 0.3.2 + react: 18.2.0 + use-sync-external-store: 1.2.2(react@18.2.0) + dev: false + /@emotion/hash@0.9.0: resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} dev: true @@ -11590,6 +11612,14 @@ packages: dev: true optional: true + /@rollup/rollup-darwin-arm64@4.21.1: + resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@rollup/rollup-darwin-x64@4.13.2: resolution: {integrity: sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==} cpu: [x64]