From 104ae251c04a87154971da6b04a851d03fd14f70 Mon Sep 17 00:00:00 2001 From: acoshift Date: Sun, 21 Jun 2026 22:30:19 +0700 Subject: [PATCH] error: rename console error actions to the error.* module Pure rename to track api#106, which promoted the shipped deployment.errors actions into a first-class error.* resource. No new UI and no behavior change; the per-deployment Errors tab and the project-level Errors view render identically. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011d4bVuGLnCbcJD9ZvastPH --- src/lib/server/mock.ts | 14 ++--- .../deployment/(detail)/errors/+page.svelte | 33 ++++++------ .../(auth)/(project)/errors/+page.svelte | 22 ++++---- src/types/api.d.ts | 54 +++++++++---------- 4 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/lib/server/mock.ts b/src/lib/server/mock.ts index 22fe76a6..7f59ed8f 100644 --- a/src/lib/server/mock.ts +++ b/src/lib/server/mock.ts @@ -1334,7 +1334,7 @@ const handlers: Record object> = { // page returns nextCursor='page2', the second returns the rest with no // further cursor. The 'sg1' location simulates a location with no log // capture (error detection unavailable). - 'deployment.errors': (args) => { + 'error.list': (args) => { if (args?.location === 'gke.cluster-sg1') { return err('api: error detection is not available for this location') } @@ -1350,7 +1350,7 @@ const handlers: Record object> = { const location = String(args?.location ?? LOCATION_ID) // Per-deployment fixture: every issue belongs to the queried deployment. - const single: Api.DeploymentErrorIssue[] = [ + const single: Api.ErrorIssue[] = [ { id: 'iss_go_nilmap', deployment: deploymentName, @@ -1434,7 +1434,7 @@ const handlers: Record object> = { // Project-wide fixture: issues span several deployments + locations and // every kind/status, enough to page across two screens. Sorted lastSeen // desc to match the page's default sort. - const wide: Api.DeploymentErrorIssue[] = [ + const wide: Api.ErrorIssue[] = [ { id: 'iss_api_go_nilmap', deployment: 'api', @@ -1542,7 +1542,7 @@ const handlers: Record object> = { ] const all = projectWide ? wide : single - const status = (args?.status as Api.DeploymentErrorStatusFilter | undefined) ?? 'open' + const status = (args?.status as Api.ErrorStatusFilter | undefined) ?? 'open' const filtered = status === 'all' ? all : all.filter((it) => it.status === status) // Two-page paging: first request (no cursor) returns the first 3, then // nextCursor='page2' yields the remainder. Pages are only meaningful when @@ -1553,7 +1553,7 @@ const handlers: Record object> = { } return ok({ issues: filtered.slice(3), nextCursor: undefined }) }, - 'deployment.errorGet': (args) => { + 'error.get': (args) => { const base = Date.now() const at = (mins: number) => new Date(base - mins * 60_000).toISOString() const samples: Record = { @@ -1607,7 +1607,7 @@ const handlers: Record object> = { } const id = String(args?.id ?? 'iss_go_nilmap') const s = samples[id] ?? samples.iss_go_nilmap - const issue: Api.DeploymentErrorIssueDetail = { + const issue: Api.ErrorIssueDetail = { id, deployment: String(args?.name ?? 'api'), location: String(args?.location ?? LOCATION_ID), @@ -1642,7 +1642,7 @@ const handlers: Record object> = { } return ok({ issue }) }, - 'deployment.errorUpdate': () => ok({}), + 'error.update': () => ok({}), 'disk.list': () => list(disks), 'disk.get': (args) => ok({ ...disks[0], name: args?.name ?? 'data', location: args?.location ?? LOCATION_ID }), diff --git a/src/routes/(auth)/(project)/deployment/(detail)/errors/+page.svelte b/src/routes/(auth)/(project)/deployment/(detail)/errors/+page.svelte index 885d6c06..889f7710 100644 --- a/src/routes/(auth)/(project)/deployment/(detail)/errors/+page.svelte +++ b/src/routes/(auth)/(project)/deployment/(detail)/errors/+page.svelte @@ -7,17 +7,18 @@ const { data }: { data: PageData } = $props() const deployment = $derived(data.deployment) - // Triage (resolve/mute/reopen) and read both ride the single deployment.logs - // permission for v1 (one surface; a dedicated deployment.errors perm may be - // split out later). Keep this in lockstep with the server-side gate. - const TRIAGE_PERMISSION = 'deployment.logs' + // Reading the issue list + detail is gated by error.list / error.get; triage + // (resolve/mute/reopen) by error.update. Keep these in lockstep with the + // server-side gate. + const READ_PERMISSION = 'error.list' + const TRIAGE_PERMISSION = 'error.update' // The list endpoint reports this when the deployment's location has no log // bucket (error detection permanently unavailable there); we match on the // stable substring rather than an error code, mirroring the API contract. const UNAVAILABLE_MARKER = 'error detection is not available for this location' - type StatusFilter = Api.DeploymentErrorStatusFilter + type StatusFilter = Api.ErrorStatusFilter const STATUS_FILTERS: { value: StatusFilter, label: string }[] = [ { value: 'open', label: 'Open' }, @@ -28,7 +29,7 @@ // Short, language-coloured kind badges. Hues are token-based so they recolour // with the theme. Kept LOCAL to this page on purpose (see PR notes). - const KIND_META: Record = { + const KIND_META: Record = { go: { label: 'Go', hue: 198 }, java: { label: 'Java', hue: 18 }, python: { label: 'Py', hue: 142 }, @@ -37,7 +38,7 @@ generic: { label: 'Generic', hue: 250 } } - function kindMeta (kind: Api.DeploymentErrorKind) { + function kindMeta (kind: Api.ErrorKind) { return KIND_META[kind] ?? { label: kind, hue: 250 } } @@ -65,7 +66,7 @@ let status = $state('open') let query = $state('') - let issues = $state([]) + let issues = $state([]) let nextCursor = $state(undefined) let loading = $state(false) let loadingMore = $state(false) @@ -79,7 +80,7 @@ // Expanded issue id → its loaded detail (or null while loading). let expandedId = $state(null) - let detail = $state(null) + let detail = $state(null) let detailLoading = $state(false) let detailError = $state('') // id currently being mutated, so only its buttons show the loading state. @@ -121,7 +122,7 @@ // new filter. expandedId = null detail = null - const resp = await api.invoke('deployment.errors', { + const resp = await api.invoke('error.list', { project: deployment.project, location: deployment.location, name: deployment.name, @@ -143,7 +144,7 @@ async function loadMore (): Promise { if (!nextCursor || loadingMore) return loadingMore = true - const resp = await api.invoke('deployment.errors', { + const resp = await api.invoke('error.list', { project: deployment.project, location: deployment.location, name: deployment.name, @@ -158,7 +159,7 @@ loadingMore = false } - async function toggleExpand (issue: Api.DeploymentErrorIssue): Promise { + async function toggleExpand (issue: Api.ErrorIssue): Promise { if (expandedId === issue.id) { expandedId = null detail = null @@ -169,7 +170,7 @@ detail = null detailError = '' detailLoading = true - const resp = await api.invoke('deployment.errorGet', { + const resp = await api.invoke('error.get', { project: deployment.project, location: deployment.location, name: deployment.name, @@ -186,9 +187,9 @@ detailLoading = false } - async function updateStatus (issue: Api.DeploymentErrorIssue, next: Api.DeploymentErrorStatus): Promise { + async function updateStatus (issue: Api.ErrorIssue, next: Api.ErrorStatus): Promise { updatingId = issue.id - const resp = await api.invoke('deployment.errorUpdate', { + const resp = await api.invoke('error.update', { project: deployment.project, location: deployment.location, name: deployment.name, @@ -661,7 +662,7 @@
You don't have access to errors
-
Viewing application errors requires the deployment.logs permission.
+
Viewing application errors requires the {READ_PERMISSION} permission.
{:else if unavailable}
diff --git a/src/routes/(auth)/(project)/errors/+page.svelte b/src/routes/(auth)/(project)/errors/+page.svelte index 6df0f66e..9807eae6 100644 --- a/src/routes/(auth)/(project)/errors/+page.svelte +++ b/src/routes/(auth)/(project)/errors/+page.svelte @@ -6,11 +6,11 @@ const { data }: { data: PageData } = $props() const project = $derived(data.project) - // Reading errors is gated by the same deployment.logs permission as the - // per-deployment Errors tab (one surface; keep in lockstep with the server). - const READ_PERMISSION = 'deployment.logs' + // Reading errors is gated by the error.list permission, matching the + // per-deployment Errors tab (keep in lockstep with the server). + const READ_PERMISSION = 'error.list' - type StatusFilter = Api.DeploymentErrorStatusFilter + type StatusFilter = Api.ErrorStatusFilter const STATUS_FILTERS: { value: StatusFilter, label: string }[] = [ { value: 'open', label: 'Open' }, @@ -21,7 +21,7 @@ // Short, language-coloured kind badges. Hues are token-based so they recolour // with the theme. Kept LOCAL, mirroring the per-deployment Errors tab. - const KIND_META: Record = { + const KIND_META: Record = { go: { label: 'Go', hue: 198 }, java: { label: 'Java', hue: 18 }, python: { label: 'Py', hue: 142 }, @@ -30,7 +30,7 @@ generic: { label: 'Generic', hue: 250 } } - function kindMeta (kind: Api.DeploymentErrorKind) { + function kindMeta (kind: Api.ErrorKind) { return KIND_META[kind] ?? { label: kind, hue: 250 } } @@ -58,7 +58,7 @@ // Deep-link to this issue on the owning deployment's Errors tab, where the // full detail + triage (resolve / mute / reopen) lives. - function issueHref (issue: Api.DeploymentErrorIssue): string { + function issueHref (issue: Api.ErrorIssue): string { const q = new URLSearchParams({ project, location: issue.location, @@ -69,7 +69,7 @@ let status = $state('open') let query = $state('') - let issues = $state([]) + let issues = $state([]) let nextCursor = $state(undefined) let loading = $state(false) let loadingMore = $state(false) @@ -107,9 +107,9 @@ // forbidden response can recover. forbidden = false errorMessage = '' - // Project-wide listing: deployment.errors with no `name` aggregates issues + // Project-wide listing: error.list with no `name` aggregates issues // across every deployment in the project. - const resp = await api.invoke('deployment.errors', { + const resp = await api.invoke('error.list', { project, status, sort: 'lastSeen' @@ -129,7 +129,7 @@ async function loadMore (): Promise { if (!nextCursor || loadingMore) return loadingMore = true - const resp = await api.invoke('deployment.errors', { + const resp = await api.invoke('error.list', { project, status, sort: 'lastSeen', diff --git a/src/types/api.d.ts b/src/types/api.d.ts index 08358bcc..d0f715b3 100644 --- a/src/types/api.d.ts +++ b/src/types/api.d.ts @@ -923,26 +923,26 @@ declare namespace Api { // Application-error detection (Sentry-lite). An "issue" is a group of // identical application-level errors (panics, exceptions, stack traces the // app prints) deduplicated by a stable server-side fingerprint. - export type DeploymentErrorKind = 'go' | 'java' | 'python' | 'node' | 'ruby' | 'generic' - export type DeploymentErrorStatus = 'open' | 'resolved' | 'muted' + export type ErrorKind = 'go' | 'java' | 'python' | 'node' | 'ruby' | 'generic' + export type ErrorStatus = 'open' | 'resolved' | 'muted' // The status query also accepts 'all' (no filter); the default is 'open'. - export type DeploymentErrorStatusFilter = DeploymentErrorStatus | 'all' - export type DeploymentErrorSort = 'lastSeen' | 'firstSeen' | 'count' + export type ErrorStatusFilter = ErrorStatus | 'all' + export type ErrorSort = 'lastSeen' | 'firstSeen' | 'count' - // One grouped issue as returned by deployment.errors (the list view). + // One grouped issue as returned by error.list (the list view). // // `deployment` + `location` identify which deployment the issue belongs to. // They are always present; on the per-deployment call they echo the queried - // deployment, and on the project-wide call (deployment.errors without a + // deployment, and on the project-wide call (error.list without a // `name`) they are the column that distinguishes issues across deployments. - export type DeploymentErrorIssue = { + export type ErrorIssue = { id: string deployment: string location: string fingerprint: string - kind: DeploymentErrorKind + kind: ErrorKind title: string - status: DeploymentErrorStatus + status: ErrorStatus count: number firstSeen: string lastSeen: string @@ -951,7 +951,7 @@ declare namespace Api { // A lightweight pointer to one occurrence of an issue (recent_events). The // full sample stack lives once on the issue; these only carry where/when. - export type DeploymentErrorOccurrence = { + export type ErrorOccurrence = { pod: string timestamp: string // object + offset locate the occurrence in the durable _errorlog stream @@ -962,56 +962,56 @@ declare namespace Api { // The detail view: all list fields plus the representative sample stack and // the recent occurrence pointers. - export type DeploymentErrorIssueDetail = DeploymentErrorIssue & { + export type ErrorIssueDetail = ErrorIssue & { sampleMessage: string - recentEvents: DeploymentErrorOccurrence[] + recentEvents: ErrorOccurrence[] } - // Args of deployment.errors (list). status defaults to 'open', sort to + // Args of error.list. status defaults to 'open', sort to // 'lastSeen'; cursor is the opaque page token from a prior nextCursor. // // `location` + `name` are optional: supplying both scopes the listing to a // single deployment, while OMITTING `name` lists error issues across every // deployment in the project (the project-wide Errors view). Each returned // issue carries its own `deployment` + `location` regardless. - export type DeploymentErrorsArgs = { + export type ErrorListArgs = { project: string location?: string name?: string - status?: DeploymentErrorStatusFilter + status?: ErrorStatusFilter limit?: number cursor?: string - sort?: DeploymentErrorSort + sort?: ErrorSort } - // Result of deployment.errors. nextCursor is non-empty while more issues + // Result of error.list. nextCursor is non-empty while more issues // remain for the current filter/sort. - export type DeploymentErrorsResult = { - issues: DeploymentErrorIssue[] + export type ErrorListResult = { + issues: ErrorIssue[] nextCursor?: string } - // Args of deployment.errorGet (detail). - export type DeploymentErrorGetArgs = { + // Args of error.get (detail). + export type ErrorGetArgs = { project: string location: string name: string id: string } - // Result of deployment.errorGet. - export type DeploymentErrorGetResult = { - issue: DeploymentErrorIssueDetail + // Result of error.get. + export type ErrorGetResult = { + issue: ErrorIssueDetail } - // Args of deployment.errorUpdate (triage). status flips the lifecycle: + // Args of error.update (triage). status flips the lifecycle: // resolved / open (reopen) / muted. - export type DeploymentErrorUpdateArgs = { + export type ErrorUpdateArgs = { project: string location: string name: string id: string - status: DeploymentErrorStatus + status: ErrorStatus } export type BillingReportProject = {