| 1 | /** |
| 2 | * What every Observability page loads first: that the viewer may see it, |
| 3 | * and which project it is. Observability is for people with a role on the |
| 4 | * project, even when its code is public: logs and request addresses can |
| 5 | * hold what only the people running it should read. Its settings, tokens |
| 6 | * and exports are for those who manage the project's integrations. |
| 7 | */ |
| 8 | import { data } from "react-router"; |
| 9 | |
| 10 | import type { Capability } from "@g1t/contracts"; |
| 11 | |
| 12 | import { requireInsider } from "./access.server"; |
| 13 | import { projects, telemetry } from "./services.server"; |
| 14 | import { parseView, filterOf, type View } from "./observability"; |
| 15 | |
| 16 | type Context = Parameters<typeof requireInsider>[0]; |
| 17 | type Params = Parameters<typeof requireInsider>[1]; |
| 18 | |
| 19 | export async function observed(context: Context, params: Params, request: Request, capability: Capability = "read") { |
| 20 | const { viewer, access, repo } = await requireInsider(context, params, capability); |
| 21 | if (!telemetry) throw data("Observability is not set up on this installation.", { status: 503 }); |
| 22 | const found = await projects.get(params.owner ?? "", params.repo ?? "", viewer); |
| 23 | if (!found.ok) throw data(null, { status: 404 }); |
| 24 | const view: View = parseView(new URL(request.url).searchParams); |
| 25 | return { |
| 26 | viewer, |
| 27 | access, |
| 28 | repo, |
| 29 | project: found.value, |
| 30 | projectId: found.value.id, |
| 31 | service: telemetry, |
| 32 | view, |
| 33 | filter: filterOf(view), |
| 34 | }; |
| 35 | } |
| 36 | |
| 37 | /** A part that fails shows as missing, with the page still up. */ |
| 38 | export function soft<T>(work: Promise<T>, what: string): Promise<T | null> { |
| 39 | return work.catch((error) => { |
| 40 | console.warn(`observability ${what}:`, error); |
| 41 | return null; |
| 42 | }); |
| 43 | } |