Misc. of sentry improvements (#12233)
This PR mixes various initiatives to improve visibility on sentry **1. Catch errors on workflow jobs** commit [catch workflowTriggerExceptions in job handle](1dbba8c9e2) @thomtrp **2. Fix type in messagingImportExceptionHandler** commit [fix type issue on messagingImportExceptionHandler](919bb3844c) @guillim **3. Catch invalid uuid errors thrown by Postgres by rightfully typing expected id as uuid** commits [use UUIDFilter instead of IDFilter to get graphqlError in case of malformed id](57cc315efe), [use UUIDFilter (2)](304553d770), [fix ids typed as UUID instead of ID](f95d6319cf) @Weiko ⚠️⚠️⚠️ when we deploy this PR we need to flush the schema types from redis as this PR changes them ⚠️⚠️⚠️ **4. Do not group UNKNOWN errors together** commit [do not group unknown errors together](c299b39c8f) Some CustomException classes have introduced UNKNOWN error codes as a default fallback error code. We use CustomException codes to group issues together, but we don't want to do it with UNKNOWN error as they may not have anything in common. For exemple [this sentry for UNKNOWN code](https://twenty-v7.sentry.io/issues/6605750776/events/a72272d8941b4fa2add9b1f39c196d3f/?environment=prod&environment=prod-eu&project=4507072499810304&query=Unknown&referrer=next-event&stream_index=0) groups together "Unknown error importing calendar events for calendar channel...", "Insufficent permissions...", to name a few. **5. Improve postgres error grouping** commit [group together postgres errors](567c25495e) Postgres error are thrown by typeORM as QueryFailedError. we have a lot of them on sentry where they are badly grouped They are currently grouped on sentry according to the stack trace, which leads them to sometimes be grouped even if they don't have anything in common : for exemple [this sentry for QueryFailedError](https://twenty-v7.sentry.io/issues/6563624590/events/2d636821e27a448595b647b4b5a7d6a8/?environment=prod&environment=prod-eu&project=4507072499810304&query=is%3Aunresolved%20%21issue.type%3A%5Bperformance_consecutive_db_queries%2Cperformance_consecutive_http%2Cperformance_file_io_main_thread%2Cperformance_db_main_thread%2Cperformance_n_plus_one_db_queries%2Cperformance_n_plus_one_api_calls%2Cperformance_p95_endpoint_regression%2Cperformance_slow_db_query%2Cperformance_render_blocking_asset_span%2Cperformance_uncompressed_assets%2Cperformance_http_overhead%2Cperformance_large_http_payload%5D%20timesSeen%3A%3E10&referrer=previous-event&sort=date&stream_index=0) groups together "user mapping not found for "postgres" and "invalide type for uuid: 'fallback-id'" to name a few. I attempted to improve the grouping by grouping them with a new custom fingerPrint composed of the [code returned by Postgres](https://www.postgresql.org/docs/current/errcodes-appendix.html) + the truncated operation name (Find, Aggregate, Check...). This is still not ideal as postgres code are quite broad - we could have the same error code for two Find operations with different causes. let's give this a try !
This commit is contained in:
@ -0,0 +1,263 @@
|
||||
// From https://www.postgresql.org/docs/current/errcodes-appendix.html
|
||||
export const POSTGRESQL_ERROR_CODES = [
|
||||
'00000', // successful_completion
|
||||
'01000', // warning
|
||||
'0100C', // dynamic_result_sets_returned
|
||||
'01008', // implicit_zero_bit_padding
|
||||
'01003', // null_value_eliminated_in_set_function
|
||||
'01007', // privilege_not_granted
|
||||
'01006', // privilege_not_revoked
|
||||
'01004', // string_data_right_truncation
|
||||
'01P01', // deprecated_feature
|
||||
'02000', // no_data
|
||||
'02001', // no_additional_dynamic_result_sets_returned
|
||||
'03000', // sql_statement_not_yet_complete
|
||||
'08000', // connection_exception
|
||||
'08003', // connection_does_not_exist
|
||||
'08006', // connection_failure
|
||||
'08001', // sqlclient_unable_to_establish_sqlconnection
|
||||
'08004', // sqlserver_rejected_establishment_of_sqlconnection
|
||||
'08007', // transaction_resolution_unknown
|
||||
'08P01', // protocol_violation
|
||||
'09000', // triggered_action_exception
|
||||
'0A000', // feature_not_supported
|
||||
'0B000', // invalid_transaction_initiation
|
||||
'0F000', // locator_exception
|
||||
'0F001', // invalid_locator_specification
|
||||
'0L000', // invalid_grantor
|
||||
'0LP01', // invalid_grant_operation
|
||||
'0P000', // invalid_role_specification
|
||||
'0Z000', // diagnostics_exception
|
||||
'0Z002', // stacked_diagnostics_accessed_without_active_handler
|
||||
'20000', // case_not_found
|
||||
'21000', // cardinality_violation
|
||||
'22000', // data_exception
|
||||
'2202E', // array_subscript_error
|
||||
'22021', // character_not_in_repertoire
|
||||
'22008', // datetime_field_overflow
|
||||
'22012', // division_by_zero
|
||||
'22005', // error_in_assignment
|
||||
'2200B', // escape_character_conflict
|
||||
'22022', // indicator_overflow
|
||||
'22015', // interval_field_overflow
|
||||
'2201E', // invalid_argument_for_logarithm
|
||||
'22014', // invalid_argument_for_ntile_function
|
||||
'22016', // invalid_argument_for_nth_value_function
|
||||
'2201F', // invalid_argument_for_power_function
|
||||
'2201G', // invalid_argument_for_width_bucket_function
|
||||
'22018', // invalid_character_value_for_cast
|
||||
'22007', // invalid_datetime_format
|
||||
'22019', // invalid_escape_character
|
||||
'2200D', // invalid_escape_octet
|
||||
'22025', // invalid_escape_sequence
|
||||
'22P06', // nonstandard_use_of_escape_character
|
||||
'22010', // invalid_indicator_parameter_value
|
||||
'22023', // invalid_parameter_value
|
||||
'22013', // invalid_preceding_or_following_size
|
||||
'2201B', // invalid_regular_expression
|
||||
'2201W', // invalid_row_count_in_limit_clause
|
||||
'2201X', // invalid_row_count_in_result_offset_clause
|
||||
'2202H', // invalid_tablesample_argument
|
||||
'2202G', // invalid_tablesample_repeat
|
||||
'22009', // invalid_time_zone_displacement_value
|
||||
'2200C', // invalid_use_of_escape_character
|
||||
'2200G', // most_specific_type_mismatch
|
||||
'22004', // null_value_not_allowed
|
||||
'22002', // null_value_no_indicator_parameter
|
||||
'22003', // numeric_value_out_of_range
|
||||
'2200H', // sequence_generator_limit_exceeded
|
||||
'22026', // string_data_length_mismatch
|
||||
'22001', // string_data_right_truncation
|
||||
'22011', // substring_error
|
||||
'22027', // trim_error
|
||||
'22024', // unterminated_c_string
|
||||
'2200F', // zero_length_character_string
|
||||
'22P01', // floating_point_exception
|
||||
'22P02', // invalid_text_representation
|
||||
'22P03', // invalid_binary_representation
|
||||
'22P04', // bad_copy_file_format
|
||||
'22P05', // untranslatable_character
|
||||
'2200L', // not_an_xml_document
|
||||
'2200M', // invalid_xml_document
|
||||
'2200N', // invalid_xml_content
|
||||
'2200S', // invalid_xml_comment
|
||||
'2200T', // invalid_xml_processing_instruction
|
||||
'22030', // duplicate_json_object_key_value
|
||||
'22031', // invalid_argument_for_sql_json_datetime_function
|
||||
'22032', // invalid_json_text
|
||||
'22033', // invalid_sql_json_subscript
|
||||
'22034', // more_than_one_sql_json_item
|
||||
'22035', // no_sql_json_item
|
||||
'22036', // non_numeric_sql_json_item
|
||||
'22037', // non_unique_keys_in_a_json_object
|
||||
'22038', // singleton_sql_json_item_required
|
||||
'22039', // sql_json_array_not_found
|
||||
'2203A', // sql_json_member_not_found
|
||||
'2203B', // sql_json_number_not_found
|
||||
'2203C', // sql_json_object_not_found
|
||||
'2203D', // too_many_json_array_elements
|
||||
'2203E', // too_many_json_object_members
|
||||
'2203F', // sql_json_scalar_required
|
||||
'2203G', // sql_json_item_cannot_be_cast_to_target_type
|
||||
'23000', // integrity_constraint_violation
|
||||
'23001', // restrict_violation
|
||||
'23502', // not_null_violation
|
||||
'23503', // foreign_key_violation
|
||||
'23505', // unique_violation
|
||||
'23514', // check_violation
|
||||
'23P01', // exclusion_violation
|
||||
'24000', // invalid_cursor_state
|
||||
'25000', // invalid_transaction_state
|
||||
'25001', // active_sql_transaction
|
||||
'25002', // branch_transaction_already_active
|
||||
'25008', // held_cursor_requires_same_isolation_level
|
||||
'25003', // inappropriate_access_mode_for_branch_transaction
|
||||
'25004', // inappropriate_isolation_level_for_branch_transaction
|
||||
'25005', // no_active_sql_transaction_for_branch_transaction
|
||||
'25006', // read_only_sql_transaction
|
||||
'25007', // schema_and_data_statement_mixing_not_supported
|
||||
'25P01', // no_active_sql_transaction
|
||||
'25P02', // in_failed_sql_transaction
|
||||
'25P03', // idle_in_transaction_session_timeout
|
||||
'25P04', // transaction_timeout
|
||||
'26000', // invalid_sql_statement_name
|
||||
'27000', // triggered_data_change_violation
|
||||
'28000', // invalid_authorization_specification
|
||||
'28P01', // invalid_password
|
||||
'2B000', // dependent_privilege_descriptors_still_exist
|
||||
'2BP01', // dependent_objects_still_exist
|
||||
'2D000', // invalid_transaction_termination
|
||||
'2F000', // sql_routine_exception
|
||||
'2F005', // function_executed_no_return_statement
|
||||
'2F002', // modifying_sql_data_not_permitted
|
||||
'2F003', // prohibited_sql_statement_attempted
|
||||
'2F004', // reading_sql_data_not_permitted
|
||||
'34000', // invalid_cursor_name
|
||||
'38000', // external_routine_exception
|
||||
'38001', // containing_sql_not_permitted
|
||||
'38002', // modifying_sql_data_not_permitted
|
||||
'38003', // prohibited_sql_statement_attempted
|
||||
'38004', // reading_sql_data_not_permitted
|
||||
'39000', // external_routine_invocation_exception
|
||||
'39001', // invalid_sqlstate_returned
|
||||
'39004', // null_value_not_allowed
|
||||
'39P01', // trigger_protocol_violated
|
||||
'39P02', // srf_protocol_violated
|
||||
'39P03', // event_trigger_protocol_violated
|
||||
'3B000', // savepoint_exception
|
||||
'3B001', // invalid_savepoint_specification
|
||||
'3D000', // invalid_catalog_name
|
||||
'3F000', // invalid_schema_name
|
||||
'40000', // transaction_rollback
|
||||
'40002', // transaction_integrity_constraint_violation
|
||||
'40001', // serialization_failure
|
||||
'40003', // statement_completion_unknown
|
||||
'40P01', // deadlock_detected
|
||||
'42000', // syntax_error_or_access_rule_violation
|
||||
'42601', // syntax_error
|
||||
'42501', // insufficient_privilege
|
||||
'42846', // cannot_coerce
|
||||
'42803', // grouping_error
|
||||
'42P20', // windowing_error
|
||||
'42P19', // invalid_recursion
|
||||
'42830', // invalid_foreign_key
|
||||
'42602', // invalid_name
|
||||
'42622', // name_too_long
|
||||
'42939', // reserved_name
|
||||
'42804', // datatype_mismatch
|
||||
'42P18', // indeterminate_datatype
|
||||
'42P21', // collation_mismatch
|
||||
'42P22', // indeterminate_collation
|
||||
'42809', // wrong_object_type
|
||||
'428C9', // generated_always
|
||||
'42703', // undefined_column
|
||||
'42883', // undefined_function
|
||||
'42P01', // undefined_table
|
||||
'42P02', // undefined_parameter
|
||||
'42704', // undefined_object
|
||||
'42701', // duplicate_column
|
||||
'42P03', // duplicate_cursor
|
||||
'42P04', // duplicate_database
|
||||
'42723', // duplicate_function
|
||||
'42P05', // duplicate_prepared_statement
|
||||
'42P06', // duplicate_schema
|
||||
'42P07', // duplicate_table
|
||||
'42712', // duplicate_alias
|
||||
'42710', // duplicate_object
|
||||
'42702', // ambiguous_column
|
||||
'42725', // ambiguous_function
|
||||
'42P08', // ambiguous_parameter
|
||||
'42P09', // ambiguous_alias
|
||||
'42P10', // invalid_column_reference
|
||||
'42611', // invalid_column_definition
|
||||
'42P11', // invalid_cursor_definition
|
||||
'42P12', // invalid_database_definition
|
||||
'42P13', // invalid_function_definition
|
||||
'42P14', // invalid_prepared_statement_definition
|
||||
'42P15', // invalid_schema_definition
|
||||
'42P16', // invalid_table_definition
|
||||
'42P17', // invalid_object_definition
|
||||
'44000', // with_check_option_violation
|
||||
'53000', // insufficient_resources
|
||||
'53100', // disk_full
|
||||
'53200', // out_of_memory
|
||||
'53300', // too_many_connections
|
||||
'53400', // configuration_limit_exceeded
|
||||
'54000', // program_limit_exceeded
|
||||
'54001', // statement_too_complex
|
||||
'54011', // too_many_columns
|
||||
'54023', // too_many_arguments
|
||||
'55000', // object_not_in_prerequisite_state
|
||||
'55006', // object_in_use
|
||||
'55P02', // cant_change_runtime_param
|
||||
'55P03', // lock_not_available
|
||||
'55P04', // unsafe_new_enum_value_usage
|
||||
'57000', // operator_intervention
|
||||
'57014', // query_canceled
|
||||
'57P01', // admin_shutdown
|
||||
'57P02', // crash_shutdown
|
||||
'57P03', // cannot_connect_now
|
||||
'57P04', // database_dropped
|
||||
'57P05', // idle_session_timeout
|
||||
'58000', // system_error
|
||||
'58030', // io_error
|
||||
'58P01', // undefined_file
|
||||
'58P02', // duplicate_file
|
||||
'F0000', // config_file_error
|
||||
'F0001', // lock_file_exists
|
||||
'HV000', // fdw_error
|
||||
'HV005', // fdw_column_name_not_found
|
||||
'HV002', // fdw_dynamic_parameter_value_needed
|
||||
'HV010', // fdw_function_sequence_error
|
||||
'HV021', // fdw_inconsistent_descriptor_information
|
||||
'HV024', // fdw_invalid_attribute_value
|
||||
'HV007', // fdw_invalid_column_name
|
||||
'HV008', // fdw_invalid_column_number
|
||||
'HV004', // fdw_invalid_data_type
|
||||
'HV006', // fdw_invalid_data_type_descriptors
|
||||
'HV091', // fdw_invalid_descriptor_field_identifier
|
||||
'HV00B', // fdw_invalid_handle
|
||||
'HV00C', // fdw_invalid_option_index
|
||||
'HV00D', // fdw_invalid_option_name
|
||||
'HV090', // fdw_invalid_string_length_or_buffer_length
|
||||
'HV00A', // fdw_invalid_string_format
|
||||
'HV009', // fdw_invalid_use_of_null_pointer
|
||||
'HV014', // fdw_too_many_handles
|
||||
'HV001', // fdw_out_of_memory
|
||||
'HV00P', // fdw_no_schemas
|
||||
'HV00J', // fdw_option_name_not_found
|
||||
'HV00K', // fdw_reply_handle
|
||||
'HV00Q', // fdw_schema_not_found
|
||||
'HV00R', // fdw_table_not_found
|
||||
'HV00L', // fdw_unable_to_create_execution
|
||||
'HV00M', // fdw_unable_to_create_reply
|
||||
'HV00N', // fdw_unable_to_establish_connection
|
||||
'P0000', // plpgsql_error
|
||||
'P0001', // raise_exception
|
||||
'P0002', // no_data_found
|
||||
'P0003', // too_many_rows
|
||||
'P0004', // assert_failure
|
||||
'XX000', // internal_error
|
||||
'XX001', // data_corrupted
|
||||
'XX002', // index_corrupted
|
||||
];
|
||||
@ -0,0 +1,7 @@
|
||||
export class PostgresException extends Error {
|
||||
readonly code: string;
|
||||
constructor(message: string, code: string) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@ -3,8 +3,10 @@ import { QueryFailedError } from 'typeorm';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
|
||||
import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { POSTGRESQL_ERROR_CODES } from 'src/engine/api/graphql/workspace-query-runner/constants/postgres-error-codes.constants';
|
||||
import { graphqlQueryRunnerExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util';
|
||||
import { handleDuplicateKeyError } from 'src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util';
|
||||
import { PostgresException } from 'src/engine/api/graphql/workspace-query-runner/utils/postgres-exception';
|
||||
import { workspaceExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util';
|
||||
import { WorkspaceQueryRunnerException } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import { RecordTransformerException } from 'src/engine/core-modules/record-transformer/record-transformer.exception';
|
||||
@ -12,8 +14,12 @@ import { recordTransformerGraphqlApiExceptionHandler } from 'src/engine/core-mod
|
||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util';
|
||||
|
||||
interface QueryFailedErrorWithCode extends QueryFailedError {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
|
||||
error: Error,
|
||||
error: QueryFailedErrorWithCode,
|
||||
context: WorkspaceQueryRunnerOptions,
|
||||
) => {
|
||||
switch (true) {
|
||||
@ -23,6 +29,11 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
|
||||
) {
|
||||
return handleDuplicateKeyError(error, context);
|
||||
}
|
||||
const errorCode = (error as QueryFailedErrorWithCode).code;
|
||||
|
||||
if (POSTGRESQL_ERROR_CODES.includes(errorCode)) {
|
||||
throw new PostgresException(error.message, errorCode);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
case error instanceof RecordTransformerException:
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { GraphQLInputObjectType, GraphQLList } from 'graphql';
|
||||
|
||||
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
export const UUIDFilterType = new GraphQLInputObjectType({
|
||||
name: 'UUIDFilter',
|
||||
fields: {
|
||||
eq: { type: UUIDScalarType },
|
||||
gt: { type: UUIDScalarType },
|
||||
gte: { type: UUIDScalarType },
|
||||
in: { type: new GraphQLList(UUIDScalarType) },
|
||||
lt: { type: UUIDScalarType },
|
||||
lte: { type: UUIDScalarType },
|
||||
neq: { type: UUIDScalarType },
|
||||
is: { type: FilterIs },
|
||||
},
|
||||
});
|
||||
@ -27,10 +27,10 @@ import {
|
||||
RawJsonFilterType,
|
||||
StringFilterType,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
||||
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
||||
import { MultiSelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/multi-select-filter.input-type';
|
||||
import { RichTextV2FilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/rich-text.input-type';
|
||||
import { SelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/select-filter.input-type';
|
||||
import { UUIDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/uuid-filter.input-type';
|
||||
import {
|
||||
BigFloatScalarType,
|
||||
UUIDScalarType,
|
||||
@ -95,14 +95,14 @@ export class TypeMapperService {
|
||||
isIdField?: boolean,
|
||||
): GraphQLInputObjectType | GraphQLScalarType | undefined {
|
||||
if (isIdField || fieldMetadataType === FieldMetadataType.RELATION) {
|
||||
return IDFilterType;
|
||||
return UUIDFilterType;
|
||||
}
|
||||
|
||||
const typeFilterMapping = new Map<
|
||||
FieldMetadataType,
|
||||
GraphQLInputObjectType | GraphQLScalarType
|
||||
>([
|
||||
[FieldMetadataType.UUID, IDFilterType],
|
||||
[FieldMetadataType.UUID, UUIDFilterType],
|
||||
[FieldMetadataType.TEXT, StringFilterType],
|
||||
[FieldMetadataType.DATE_TIME, DateFilterType],
|
||||
[FieldMetadataType.DATE, DateFilterType],
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { GraphQLBoolean, GraphQLID, GraphQLInt, GraphQLString } from 'graphql';
|
||||
import { GraphQLBoolean, GraphQLInt, GraphQLString } from 'graphql';
|
||||
|
||||
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
|
||||
describe('getResolverArgs', () => {
|
||||
@ -44,11 +45,11 @@ describe('getResolverArgs', () => {
|
||||
},
|
||||
},
|
||||
updateOne: {
|
||||
id: { type: GraphQLID, isNullable: false },
|
||||
id: { type: UUIDScalarType, isNullable: false },
|
||||
data: { kind: InputTypeDefinitionKind.Update, isNullable: false },
|
||||
},
|
||||
deleteOne: {
|
||||
id: { type: GraphQLID, isNullable: false },
|
||||
id: { type: UUIDScalarType, isNullable: false },
|
||||
},
|
||||
restoreMany: {
|
||||
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { GraphQLBoolean, GraphQLID, GraphQLInt, GraphQLString } from 'graphql';
|
||||
import { GraphQLBoolean, GraphQLInt, GraphQLString } from 'graphql';
|
||||
|
||||
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { ArgMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
|
||||
|
||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
export const getResolverArgs = (
|
||||
type: WorkspaceResolverBuilderMethodNames,
|
||||
@ -77,7 +78,7 @@ export const getResolverArgs = (
|
||||
case 'updateOne':
|
||||
return {
|
||||
id: {
|
||||
type: GraphQLID,
|
||||
type: UUIDScalarType,
|
||||
isNullable: false,
|
||||
},
|
||||
data: {
|
||||
@ -88,7 +89,7 @@ export const getResolverArgs = (
|
||||
case 'findDuplicates':
|
||||
return {
|
||||
ids: {
|
||||
type: GraphQLID,
|
||||
type: UUIDScalarType,
|
||||
isNullable: true,
|
||||
isArray: true,
|
||||
},
|
||||
@ -101,14 +102,14 @@ export const getResolverArgs = (
|
||||
case 'deleteOne':
|
||||
return {
|
||||
id: {
|
||||
type: GraphQLID,
|
||||
type: UUIDScalarType,
|
||||
isNullable: false,
|
||||
},
|
||||
};
|
||||
case 'destroyOne':
|
||||
return {
|
||||
id: {
|
||||
type: GraphQLID,
|
||||
type: UUIDScalarType,
|
||||
isNullable: false,
|
||||
},
|
||||
};
|
||||
@ -133,7 +134,7 @@ export const getResolverArgs = (
|
||||
case 'restoreOne':
|
||||
return {
|
||||
id: {
|
||||
type: GraphQLID,
|
||||
type: UUIDScalarType,
|
||||
isNullable: false,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ExceptionHandlerOptions } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-options.interface';
|
||||
|
||||
import { PostgresException } from 'src/engine/api/graphql/workspace-query-runner/utils/postgres-exception';
|
||||
import { ExceptionHandlerDriverInterface } from 'src/engine/core-modules/exception-handler/interfaces';
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
@ -55,7 +57,10 @@ export class ExceptionHandlerSentryDriver
|
||||
});
|
||||
}
|
||||
|
||||
if (exception instanceof CustomException) {
|
||||
if (
|
||||
exception instanceof CustomException &&
|
||||
exception.code !== 'UNKNOWN'
|
||||
) {
|
||||
scope.setTag('customExceptionCode', exception.code);
|
||||
scope.setFingerprint([exception.code]);
|
||||
exception.name = exception.code
|
||||
@ -67,6 +72,19 @@ export class ExceptionHandlerSentryDriver
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
if (exception instanceof PostgresException) {
|
||||
scope.setTag('postgresSqlErrorCode', exception.code);
|
||||
const fingerPrint = [exception.code];
|
||||
const genericOperationName = // truncates to first word: FindOnePerson -> Find, AggregateCompanies -> Aggregate, ...
|
||||
options?.operation?.name?.match(/^[A-Z][a-z]*/)?.[0];
|
||||
|
||||
if (isDefined(genericOperationName)) {
|
||||
fingerPrint.push(genericOperationName);
|
||||
}
|
||||
scope.setFingerprint(fingerPrint);
|
||||
exception.name = exception.message;
|
||||
}
|
||||
|
||||
const eventId = Sentry.captureException(exception, {
|
||||
contexts: {
|
||||
GraphQL: {
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { Field, ID, InputType, registerEnumType } from '@nestjs/graphql';
|
||||
import { Field, InputType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { IsArray, IsOptional } from 'class-validator';
|
||||
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { DateScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import {
|
||||
DateScalarType,
|
||||
UUIDScalarType,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class ObjectRecordFilterInput implements Partial<ObjectRecordFilter> {
|
||||
@ -22,9 +25,9 @@ export class ObjectRecordFilterInput implements Partial<ObjectRecordFilter> {
|
||||
@IsArray()
|
||||
or?: ObjectRecordFilterInput[];
|
||||
|
||||
@Field(() => IDFilterType, { nullable: true })
|
||||
@Field(() => UUIDFilterType, { nullable: true })
|
||||
@IsOptional()
|
||||
id?: IDFilterType | null;
|
||||
id?: UUIDFilterType | null;
|
||||
|
||||
@Field(() => DateFilterType, { nullable: true })
|
||||
createdAt?: DateFilterType | null;
|
||||
@ -36,33 +39,33 @@ export class ObjectRecordFilterInput implements Partial<ObjectRecordFilter> {
|
||||
deletedAt?: DateFilterType | null;
|
||||
}
|
||||
|
||||
@InputType('IDFilter')
|
||||
class IDFilterType {
|
||||
@Field(() => ID, { nullable: true })
|
||||
@InputType('UUIDFilter')
|
||||
class UUIDFilterType {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsOptional()
|
||||
eq?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsOptional()
|
||||
gt?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsOptional()
|
||||
gte?: string;
|
||||
|
||||
@Field(() => [ID], { nullable: true })
|
||||
@Field(() => [UUIDScalarType], { nullable: true })
|
||||
@IsOptional()
|
||||
in?: string[];
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsOptional()
|
||||
lt?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsOptional()
|
||||
lte?: string;
|
||||
|
||||
@Field(() => ID, { nullable: true })
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsOptional()
|
||||
neq?: string;
|
||||
|
||||
|
||||
@ -9,28 +9,34 @@ import {
|
||||
WorkflowTriggerExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||
|
||||
export const handleWorkflowTriggerException = (
|
||||
exception: WorkflowTriggerException,
|
||||
) => {
|
||||
switch (exception.code) {
|
||||
case WorkflowTriggerExceptionCode.INVALID_INPUT:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION:
|
||||
case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
||||
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
||||
throw new UserInputError(exception.message);
|
||||
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
||||
throw new NotFoundError(exception.message);
|
||||
case WorkflowTriggerExceptionCode.INTERNAL_ERROR:
|
||||
throw exception;
|
||||
default: {
|
||||
const _exhaustiveCheck: never = exception.code;
|
||||
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Catch(WorkflowTriggerException)
|
||||
export class WorkflowTriggerGraphqlApiExceptionFilter
|
||||
implements ExceptionFilter
|
||||
{
|
||||
catch(exception: WorkflowTriggerException) {
|
||||
switch (exception.code) {
|
||||
case WorkflowTriggerExceptionCode.INVALID_INPUT:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION:
|
||||
case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
||||
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
||||
throw new UserInputError(exception.message);
|
||||
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
||||
throw new NotFoundError(exception.message);
|
||||
case WorkflowTriggerExceptionCode.INTERNAL_ERROR:
|
||||
throw exception;
|
||||
default: {
|
||||
const _exhaustiveCheck: never = exception.code;
|
||||
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
handleWorkflowTriggerException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
@ -35,7 +37,7 @@ export class MessageImportExceptionHandlerService {
|
||||
) {}
|
||||
|
||||
public async handleDriverException(
|
||||
exception: MessageImportDriverException,
|
||||
exception: MessageImportDriverException | Error,
|
||||
syncStep: MessageImportSyncStep,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
@ -43,49 +45,53 @@ export class MessageImportExceptionHandlerService {
|
||||
>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
switch (exception.code) {
|
||||
case MessageImportDriverExceptionCode.NOT_FOUND:
|
||||
await this.handleNotFoundException(
|
||||
syncStep,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.TEMPORARY_ERROR:
|
||||
case MessageNetworkExceptionCode.ECONNABORTED:
|
||||
case MessageNetworkExceptionCode.ENOTFOUND:
|
||||
case MessageNetworkExceptionCode.ECONNRESET:
|
||||
case MessageNetworkExceptionCode.ETIMEDOUT:
|
||||
case MessageNetworkExceptionCode.ERR_NETWORK:
|
||||
await this.handleTemporaryException(
|
||||
syncStep,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
exception,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
||||
await this.handleInsufficientPermissionsException(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR:
|
||||
await this.handlePermanentException(
|
||||
exception,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.UNKNOWN:
|
||||
case MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR:
|
||||
default:
|
||||
await this.handleUnknownException(
|
||||
exception,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
if (exception instanceof MessageImportDriverException) {
|
||||
switch (exception.code) {
|
||||
case MessageImportDriverExceptionCode.NOT_FOUND:
|
||||
await this.handleNotFoundException(
|
||||
syncStep,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.TEMPORARY_ERROR:
|
||||
case MessageNetworkExceptionCode.ECONNABORTED:
|
||||
case MessageNetworkExceptionCode.ENOTFOUND:
|
||||
case MessageNetworkExceptionCode.ECONNRESET:
|
||||
case MessageNetworkExceptionCode.ETIMEDOUT:
|
||||
case MessageNetworkExceptionCode.ERR_NETWORK:
|
||||
await this.handleTemporaryException(
|
||||
syncStep,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
exception,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
||||
await this.handleInsufficientPermissionsException(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR:
|
||||
await this.handlePermanentException(
|
||||
exception,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.UNKNOWN:
|
||||
case MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR:
|
||||
default:
|
||||
await this.handleUnknownException(
|
||||
exception,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
await this.handleUnknownException(exception, messageChannel, workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,7 +178,7 @@ export class MessageImportExceptionHandlerService {
|
||||
}
|
||||
|
||||
private async handleUnknownException(
|
||||
exception: MessageImportDriverException,
|
||||
exception: MessageImportDriverException | Error,
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
@ -183,7 +189,9 @@ export class MessageImportExceptionHandlerService {
|
||||
);
|
||||
|
||||
const messageImportException = new MessageImportException(
|
||||
exception.message,
|
||||
isDefined(exception.name)
|
||||
? `${exception.name}: ${exception.message}`
|
||||
: exception.message,
|
||||
MessageImportExceptionCode.UNKNOWN,
|
||||
);
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { handleWorkflowTriggerException } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import {
|
||||
@ -106,7 +107,7 @@ export class WorkflowTriggerJob {
|
||||
jobName: WorkflowTriggerJob.name,
|
||||
jobId: data.workflowId,
|
||||
});
|
||||
throw e;
|
||||
handleWorkflowTriggerException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user