mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 13:02:15 +03:00
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:
parent
e9bc13b5fa
commit
0d00e3d62d
@ -49,7 +49,7 @@ export const httpExceptionHandler = (
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
error = new BaseGraphQLError(
|
error = new BaseGraphQLError(
|
||||||
exception.message,
|
'Internal Server Error',
|
||||||
exception.getStatus().toString(),
|
exception.getStatus().toString(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,37 @@
|
|||||||
import { ModuleRef } from "@nestjs/core";
|
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 { QueueJobOptions } from 'src/integrations/message-queue/drivers/interfaces/job-options.interface';
|
||||||
import { MessageQueueJob, MessageQueueJobData } from "src/integrations/message-queue/interfaces/message-queue-job.interface";
|
import { MessageQueueDriver } from 'src/integrations/message-queue/drivers/interfaces/message-queue-driver.interface';
|
||||||
import { MessageQueue } from "src/integrations/message-queue/message-queue.constants";
|
import {
|
||||||
import { MessageQueueModule } from "src/integrations/message-queue/message-queue.module";
|
MessageQueueJob,
|
||||||
import { getJobClassName } from "src/integrations/message-queue/utils/get-job-class-name.util";
|
MessageQueueJobData,
|
||||||
import { QueueWorkerModule } from "src/queue-worker.module";
|
} 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 {
|
export class SyncDriver implements MessageQueueDriver {
|
||||||
constructor(private readonly jobsModuleRef: ModuleRef) {}
|
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 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);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -133,6 +133,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
isCustom: true,
|
isCustom: true,
|
||||||
targetColumnMap: {},
|
targetColumnMap: {},
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
isNullable: true,
|
||||||
type: FieldMetadataType.RELATION,
|
type: FieldMetadataType.RELATION,
|
||||||
objectMetadataId: relationMetadataInput.fromObjectMetadataId,
|
objectMetadataId: relationMetadataInput.fromObjectMetadataId,
|
||||||
workspaceId: relationMetadataInput.workspaceId,
|
workspaceId: relationMetadataInput.workspaceId,
|
||||||
@ -150,6 +151,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
: relationMetadataInput.toName,
|
: relationMetadataInput.toName,
|
||||||
},
|
},
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
isNullable: true,
|
||||||
type: FieldMetadataType.RELATION,
|
type: FieldMetadataType.RELATION,
|
||||||
objectMetadataId: relationMetadataInput.toObjectMetadataId,
|
objectMetadataId: relationMetadataInput.toObjectMetadataId,
|
||||||
workspaceId: relationMetadataInput.workspaceId,
|
workspaceId: relationMetadataInput.workspaceId,
|
||||||
@ -167,6 +169,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
value: foreignKeyColumnName,
|
value: foreignKeyColumnName,
|
||||||
},
|
},
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
isNullable: true,
|
||||||
// Should not be visible on the front side
|
// Should not be visible on the front side
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
type: FieldMetadataType.UUID,
|
type: FieldMetadataType.UUID,
|
||||||
@ -202,6 +205,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
action: WorkspaceMigrationColumnActionType.CREATE,
|
||||||
columnName: foreignKeyColumnName,
|
columnName: foreignKeyColumnName,
|
||||||
columnType: 'uuid',
|
columnType: 'uuid',
|
||||||
|
isNullable: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -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 { IConnection } from 'src/utils/pagination/interfaces/connection.interface';
|
||||||
import {
|
import {
|
||||||
@ -18,8 +23,10 @@ import {
|
|||||||
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory';
|
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 { 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 { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface';
|
||||||
import {
|
import {
|
||||||
@ -34,6 +41,7 @@ export class WorkspaceQueryRunnerService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
|
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findMany<
|
async findMany<
|
||||||
@ -44,14 +52,23 @@ export class WorkspaceQueryRunnerService {
|
|||||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<IConnection<Record> | undefined> {
|
): Promise<IConnection<Record> | undefined> {
|
||||||
const { workspaceId, targetTableName } = options;
|
try {
|
||||||
const query = await this.workspaceQueryBuilderFactory.findMany(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.findMany(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
|
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<
|
async findOne<
|
||||||
@ -61,40 +78,58 @@ export class WorkspaceQueryRunnerService {
|
|||||||
args: FindOneResolverArgs<Filter>,
|
args: FindOneResolverArgs<Filter>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record | undefined> {
|
): Promise<Record | undefined> {
|
||||||
if (!args.filter || Object.keys(args.filter).length === 0) {
|
try {
|
||||||
throw new BadRequestException('Missing filter argument');
|
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(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.findOne(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
const parsedResult = this.parseResult<IConnection<Record>>(
|
const result = await this.execute(query, workspaceId);
|
||||||
result,
|
const parsedResult = this.parseResult<IConnection<Record>>(
|
||||||
targetTableName,
|
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>(
|
async createMany<Record extends IRecord = IRecord>(
|
||||||
args: CreateManyResolverArgs<Record>,
|
args: CreateManyResolverArgs<Record>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record[] | undefined> {
|
): Promise<Record[] | undefined> {
|
||||||
const { workspaceId, targetTableName } = options;
|
try {
|
||||||
const query = await this.workspaceQueryBuilderFactory.createMany(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.createMany(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'insertInto',
|
'insertInto',
|
||||||
)?.records;
|
)?.records;
|
||||||
|
} catch (exception) {
|
||||||
|
const error = globalExceptionHandler(
|
||||||
|
exception,
|
||||||
|
this.exceptionHandlerService,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOne<Record extends IRecord = IRecord>(
|
async createOne<Record extends IRecord = IRecord>(
|
||||||
@ -110,54 +145,81 @@ export class WorkspaceQueryRunnerService {
|
|||||||
args: UpdateOneResolverArgs<Record>,
|
args: UpdateOneResolverArgs<Record>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record | undefined> {
|
): Promise<Record | undefined> {
|
||||||
const { workspaceId, targetTableName } = options;
|
try {
|
||||||
const query = await this.workspaceQueryBuilderFactory.updateOne(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.updateOne(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'update',
|
'update',
|
||||||
)?.records?.[0];
|
)?.records?.[0];
|
||||||
|
} catch (exception) {
|
||||||
|
const error = globalExceptionHandler(
|
||||||
|
exception,
|
||||||
|
this.exceptionHandlerService,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOne<Record extends IRecord = IRecord>(
|
async deleteOne<Record extends IRecord = IRecord>(
|
||||||
args: DeleteOneResolverArgs,
|
args: DeleteOneResolverArgs,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record | undefined> {
|
): Promise<Record | undefined> {
|
||||||
const { workspaceId, targetTableName } = options;
|
try {
|
||||||
const query = await this.workspaceQueryBuilderFactory.deleteOne(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.deleteOne(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'deleteFrom',
|
'deleteFrom',
|
||||||
)?.records?.[0];
|
)?.records?.[0];
|
||||||
|
} catch (exception) {
|
||||||
|
const error = globalExceptionHandler(
|
||||||
|
exception,
|
||||||
|
this.exceptionHandlerService,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMany<Record extends IRecord = IRecord>(
|
async updateMany<Record extends IRecord = IRecord>(
|
||||||
args: UpdateManyResolverArgs<Record>,
|
args: UpdateManyResolverArgs<Record>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record[] | undefined> {
|
): Promise<Record[] | undefined> {
|
||||||
const { workspaceId, targetTableName } = options;
|
try {
|
||||||
const query = await this.workspaceQueryBuilderFactory.updateMany(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.updateMany(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'update',
|
'update',
|
||||||
)?.records;
|
)?.records;
|
||||||
|
} catch (exception) {
|
||||||
|
const error = globalExceptionHandler(
|
||||||
|
exception,
|
||||||
|
this.exceptionHandlerService,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMany<
|
async deleteMany<
|
||||||
@ -167,18 +229,27 @@ export class WorkspaceQueryRunnerService {
|
|||||||
args: DeleteManyResolverArgs<Filter>,
|
args: DeleteManyResolverArgs<Filter>,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record[] | undefined> {
|
): Promise<Record[] | undefined> {
|
||||||
const { workspaceId, targetTableName } = options;
|
try {
|
||||||
const query = await this.workspaceQueryBuilderFactory.deleteMany(
|
const { workspaceId, targetTableName } = options;
|
||||||
args,
|
const query = await this.workspaceQueryBuilderFactory.deleteMany(
|
||||||
options,
|
args,
|
||||||
);
|
options,
|
||||||
const result = await this.execute(query, workspaceId);
|
);
|
||||||
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
result,
|
result,
|
||||||
targetTableName,
|
targetTableName,
|
||||||
'deleteFrom',
|
'deleteFrom',
|
||||||
)?.records;
|
)?.records;
|
||||||
|
} catch (exception) {
|
||||||
|
const error = globalExceptionHandler(
|
||||||
|
exception,
|
||||||
|
this.exceptionHandlerService,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
@ -214,12 +285,12 @@ export class WorkspaceQueryRunnerService {
|
|||||||
const result = graphqlResult?.[0]?.resolve?.data?.[entityKey];
|
const result = graphqlResult?.[0]?.resolve?.data?.[entityKey];
|
||||||
const errors = graphqlResult?.[0]?.resolve?.errors;
|
const errors = graphqlResult?.[0]?.resolve?.errors;
|
||||||
|
|
||||||
if (Array.isArray(errors) && errors.length > 0) {
|
|
||||||
console.error(`GraphQL errors on ${command}${targetTableName}`, errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new BadRequestException('Malformed result from GraphQL query');
|
throw new InternalServerErrorException(
|
||||||
|
`GraphQL errors on ${command}${targetTableName}: ${JSON.stringify(
|
||||||
|
errors,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseResult(result);
|
return parseResult(result);
|
||||||
|
Loading…
Reference in New Issue
Block a user