feat(docs): add error code filtering to GraphQL errors endpoint (#36284)

- Add support for filtering errors by specific error codes
- Implement `errors` query that accepts array of error code IDs
- Add tests for error code filtering functionality
- Update GraphQL schema with new error filtering capabilities
This commit is contained in:
Charis
2025-06-10 10:55:19 -04:00
committed by GitHub
parent ec475cc730
commit fba3f7bd45
4 changed files with 140 additions and 7 deletions

View File

@@ -151,6 +151,9 @@ type RootQueryType {
"""Filter errors by a specific Supabase service"""
service: Service
"""Filter errors by a specific error code"""
code: String
): ErrorCollection
}

View File

@@ -353,4 +353,106 @@ describe('/api/graphql errors collection', () => {
)
}
})
it('filters by code when code argument is provided', async () => {
// First, get the first error code from the database to test with
const { data: dbErrors } = await supabase()
.schema('content')
.from('error')
.select('code')
.is('deleted_at', null)
.limit(1)
expect(dbErrors).not.toBe(null)
expect(dbErrors).toHaveLength(1)
const testCode = dbErrors![0].code
const codeFilterQuery = `
query {
errors(code: "${testCode}") {
totalCount
nodes {
code
service
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: codeFilterQuery }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
// Verify all returned errors have the specified code
expect(json.data.errors.nodes.length).toBeGreaterThan(0)
expect(json.data.errors.nodes.every((e: any) => e.code === testCode)).toBe(true)
})
it('filters by both service and code when both arguments are provided', async () => {
// Get an error that exists for AUTH service
const { data: authError } = await supabase()
.schema('content')
.from('error')
.select('code, ...service(service:name)')
.is('deleted_at', null)
.eq('service.name', 'AUTH')
.limit(1)
expect(authError).not.toBe(null)
expect(authError).toHaveLength(1)
const testCode = authError![0].code
const bothFiltersQuery = `
query {
errors(service: AUTH, code: "${testCode}") {
totalCount
nodes {
code
service
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: bothFiltersQuery }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
// Verify all returned errors match both filters
expect(json.data.errors.nodes.length).toBeGreaterThan(0)
expect(
json.data.errors.nodes.every((e: any) => e.code === testCode && e.service === 'AUTH')
).toBe(true)
})
it('returns empty list when code filter matches no errors', async () => {
const nonExistentCodeQuery = `
query {
errors(code: "NONEXISTENT_CODE_12345") {
totalCount
nodes {
code
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: nonExistentCodeQuery }),
})
const result = await POST(request)
const json = await result.json()
expect(json.errors).toBeUndefined()
expect(json.data.errors.totalCount).toBe(0)
expect(json.data.errors.nodes).toHaveLength(0)
})
})

View File

@@ -22,7 +22,10 @@ export const SERVICES = {
} as const
type Service = keyof typeof SERVICES
type ErrorCollectionFetch = CollectionFetch<ErrorModel, { service?: Service }>['fetch']
type ErrorCollectionFetch = CollectionFetch<
ErrorModel,
{ service?: Service; code?: string }
>['fetch']
export class ErrorModel {
public id: string
@@ -96,15 +99,17 @@ export class ErrorModel {
const PAGE_SIZE = 20
const limit = args?.first ?? args?.last ?? PAGE_SIZE
const service = args?.additionalArgs?.service as Service | undefined
const code = args?.additionalArgs?.code as string | undefined
const [countResult, errorCodesResult] = await Promise.all([
fetchTotalErrorCount(service),
fetchTotalErrorCount(service, code),
fetchErrorDescriptions({
after: args?.after ?? undefined,
before: args?.before ?? undefined,
reverse: !!args?.last,
limit: limit + 1,
service,
code,
}),
])
@@ -127,7 +132,10 @@ export class ErrorModel {
}
}
async function fetchTotalErrorCount(service?: Service): Promise<Result<number, PostgrestError>> {
async function fetchTotalErrorCount(
service?: Service,
code?: string
): Promise<Result<number, PostgrestError>> {
const query = supabase()
.schema('content')
.from('error')
@@ -138,6 +146,10 @@ async function fetchTotalErrorCount(service?: Service): Promise<Result<number, P
query.eq('service.name', service)
}
if (code) {
query.eq('code', code)
}
const { count, error } = await query
if (error) {
return Result.error(error)
@@ -159,12 +171,14 @@ async function fetchErrorDescriptions({
reverse,
limit,
service,
code,
}: {
after?: string
before?: string
reverse: boolean
limit: number
service?: Service
code?: string
}): Promise<Result<ErrorDescription[], PostgrestError>> {
const query = supabase()
.schema('content')
@@ -176,6 +190,9 @@ async function fetchErrorDescriptions({
if (service) {
query.eq('service.name', service)
}
if (code) {
query.eq('code', code)
}
if (after != undefined) {
query.gt('id', after)
}

View File

@@ -61,18 +61,25 @@ async function resolveErrors(
return (
await Result.tryCatchFlat(
async (...args) => {
const fetch: CollectionFetch<ErrorModel, { service?: Service }, ApiError>['fetch'] = async (
fetchArgs
) => {
const fetch: CollectionFetch<
ErrorModel,
{ service?: Service; code?: string },
ApiError
>['fetch'] = async (fetchArgs) => {
const result = await ErrorModel.loadErrors({
...fetchArgs,
additionalArgs: {
service: args[0].service ?? undefined,
code: args[0].code ?? undefined,
},
})
return result.mapError((error) => new ApiError('Failed to resolve error codes', error))
}
return await GraphQLCollectionBuilder.create<ErrorModel, { service?: Service }, ApiError>({
return await GraphQLCollectionBuilder.create<
ErrorModel,
{ service?: Service; code?: string },
ApiError
>({
fetch,
args: {
...args[0],
@@ -122,6 +129,10 @@ export const errorsRoot = {
type: GraphQLEnumTypeService,
description: 'Filter errors by a specific Supabase service',
},
code: {
type: GraphQLString,
description: 'Filter errors by a specific error code',
},
},
type: createCollectionType(GraphQLObjectTypeError),
resolve: resolveErrors,