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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user