send pg graphql exception to sentry + fix missing nullable for relations (#3101)

* Send pg_graphql errors to sentry

* Send pg_graphql errors to sentry

* fix

* fix

* fix

* fix relation nullable
This commit is contained in:
Weiko 2023-12-21 16:07:25 +01:00 committed by GitHub
parent e9bc13b5fa
commit 0d00e3d62d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 188 additions and 97 deletions

View File

@ -49,7 +49,7 @@ export const httpExceptionHandler = (
);
} else {
error = new BaseGraphQLError(
exception.message,
'Internal Server Error',
exception.getStatus().toString(),
);
}

View File

@ -1,21 +1,37 @@
import { ModuleRef } from "@nestjs/core";
import { QueueJobOptions } from "src/integrations/message-queue/drivers/interfaces/job-options.interface";
import { MessageQueueDriver } from "src/integrations/message-queue/drivers/interfaces/message-queue-driver.interface";
import { MessageQueueJob, MessageQueueJobData } from "src/integrations/message-queue/interfaces/message-queue-job.interface";
import { MessageQueue } from "src/integrations/message-queue/message-queue.constants";
import { MessageQueueModule } from "src/integrations/message-queue/message-queue.module";
import { getJobClassName } from "src/integrations/message-queue/utils/get-job-class-name.util";
import { QueueWorkerModule } from "src/queue-worker.module";
import { ModuleRef } from '@nestjs/core';
import { QueueJobOptions } from 'src/integrations/message-queue/drivers/interfaces/job-options.interface';
import { MessageQueueDriver } from 'src/integrations/message-queue/drivers/interfaces/message-queue-driver.interface';
import {
MessageQueueJob,
MessageQueueJobData,
} from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { getJobClassName } from 'src/integrations/message-queue/utils/get-job-class-name.util';
export class SyncDriver implements MessageQueueDriver {
constructor(private readonly jobsModuleRef: ModuleRef) {}
async add<T extends MessageQueueJobData>(_queueName: MessageQueue, jobName: string, data: T, _options?: QueueJobOptions | undefined): Promise<void> {
async add<T extends MessageQueueJobData>(
_queueName: MessageQueue,
jobName: string,
data: T,
_options?: QueueJobOptions | undefined,
): Promise<void> {
const jobClassName = getJobClassName(jobName);
const job: MessageQueueJob<MessageQueueJobData> = this.jobsModuleRef.get(jobClassName, { strict: true });
const job: MessageQueueJob<MessageQueueJobData> = this.jobsModuleRef.get(
jobClassName,
{ strict: true },
);
return await job.handle(data);
}
work<T>(queueName: MessageQueue, handler: ({ data, id }: { data: T; id: string; }) => void | Promise<void>) {
work<T>(
queueName: MessageQueue,
handler: ({ data, id }: { data: T; id: string }) => void | Promise<void>,
) {
return;
}
}
}

View File

@ -133,6 +133,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
isCustom: true,
targetColumnMap: {},
isActive: true,
isNullable: true,
type: FieldMetadataType.RELATION,
objectMetadataId: relationMetadataInput.fromObjectMetadataId,
workspaceId: relationMetadataInput.workspaceId,
@ -150,6 +151,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
: relationMetadataInput.toName,
},
isActive: true,
isNullable: true,
type: FieldMetadataType.RELATION,
objectMetadataId: relationMetadataInput.toObjectMetadataId,
workspaceId: relationMetadataInput.workspaceId,
@ -167,6 +169,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
value: foreignKeyColumnName,
},
isActive: true,
isNullable: true,
// Should not be visible on the front side
isSystem: true,
type: FieldMetadataType.UUID,
@ -202,6 +205,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: foreignKeyColumnName,
columnType: 'uuid',
isNullable: true,
},
],
},

View File

@ -1,4 +1,9 @@
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import {
BadRequestException,
Injectable,
InternalServerErrorException,
Logger,
} from '@nestjs/common';
import { IConnection } from 'src/utils/pagination/interfaces/connection.interface';
import {
@ -18,8 +23,10 @@ import {
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory';
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service';
import { globalExceptionHandler } from 'src/filters/utils/global-exception-handler.util';
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface';
import {
@ -34,6 +41,7 @@ export class WorkspaceQueryRunnerService {
constructor(
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly exceptionHandlerService: ExceptionHandlerService,
) {}
async findMany<
@ -44,14 +52,23 @@ export class WorkspaceQueryRunnerService {
args: FindManyResolverArgs<Filter, OrderBy>,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<Record> | undefined> {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.findMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
try {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.findMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<IConnection<Record>>(result, targetTableName, '');
return this.parseResult<IConnection<Record>>(result, targetTableName, '');
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async findOne<
@ -61,40 +78,58 @@ export class WorkspaceQueryRunnerService {
args: FindOneResolverArgs<Filter>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record | undefined> {
if (!args.filter || Object.keys(args.filter).length === 0) {
throw new BadRequestException('Missing filter argument');
}
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.findOne(
args,
options,
);
const result = await this.execute(query, workspaceId);
const parsedResult = this.parseResult<IConnection<Record>>(
result,
targetTableName,
'',
);
try {
if (!args.filter || Object.keys(args.filter).length === 0) {
throw new BadRequestException('Missing filter argument');
}
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.findOne(
args,
options,
);
const result = await this.execute(query, workspaceId);
const parsedResult = this.parseResult<IConnection<Record>>(
result,
targetTableName,
'',
);
return parsedResult?.edges?.[0]?.node;
return parsedResult?.edges?.[0]?.node;
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async createMany<Record extends IRecord = IRecord>(
args: CreateManyResolverArgs<Record>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.createMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
try {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.createMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'insertInto',
)?.records;
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'insertInto',
)?.records;
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async createOne<Record extends IRecord = IRecord>(
@ -110,54 +145,81 @@ export class WorkspaceQueryRunnerService {
args: UpdateOneResolverArgs<Record>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record | undefined> {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.updateOne(
args,
options,
);
const result = await this.execute(query, workspaceId);
try {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.updateOne(
args,
options,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'update',
)?.records?.[0];
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'update',
)?.records?.[0];
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async deleteOne<Record extends IRecord = IRecord>(
args: DeleteOneResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<Record | undefined> {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.deleteOne(
args,
options,
);
const result = await this.execute(query, workspaceId);
try {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.deleteOne(
args,
options,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'deleteFrom',
)?.records?.[0];
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'deleteFrom',
)?.records?.[0];
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async updateMany<Record extends IRecord = IRecord>(
args: UpdateManyResolverArgs<Record>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.updateMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
try {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.updateMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'update',
)?.records;
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'update',
)?.records;
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async deleteMany<
@ -167,18 +229,27 @@ export class WorkspaceQueryRunnerService {
args: DeleteManyResolverArgs<Filter>,
options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.deleteMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
try {
const { workspaceId, targetTableName } = options;
const query = await this.workspaceQueryBuilderFactory.deleteMany(
args,
options,
);
const result = await this.execute(query, workspaceId);
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'deleteFrom',
)?.records;
return this.parseResult<PGGraphQLMutation<Record>>(
result,
targetTableName,
'deleteFrom',
)?.records;
} catch (exception) {
const error = globalExceptionHandler(
exception,
this.exceptionHandlerService,
);
return Promise.reject(error);
}
}
async execute(
@ -214,12 +285,12 @@ export class WorkspaceQueryRunnerService {
const result = graphqlResult?.[0]?.resolve?.data?.[entityKey];
const errors = graphqlResult?.[0]?.resolve?.errors;
if (Array.isArray(errors) && errors.length > 0) {
console.error(`GraphQL errors on ${command}${targetTableName}`, errors);
}
if (!result) {
throw new BadRequestException('Malformed result from GraphQL query');
throw new InternalServerErrorException(
`GraphQL errors on ${command}${targetTableName}: ${JSON.stringify(
errors,
)}`,
);
}
return parseResult(result);