diff --git a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx index 5104ecdbb..b1f663065 100644 --- a/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/snack-bar-manager/components/SnackBar.tsx @@ -1,7 +1,7 @@ -import { ComponentPropsWithoutRef, ReactNode, useMemo } from 'react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { isUndefined } from '@sniptt/guards'; +import { ComponentPropsWithoutRef, ReactNode, useMemo } from 'react'; import { IconAlertTriangle, IconInfoCircle, @@ -46,7 +46,6 @@ const StyledContainer = styled.div` box-shadow: ${({ theme }) => theme.boxShadow.strong}; box-sizing: border-box; cursor: pointer; - height: 61px; padding: ${({ theme }) => theme.spacing(2)}; position: relative; width: 296px; @@ -90,7 +89,6 @@ const StyledDescription = styled.div` padding-left: ${({ theme }) => theme.spacing(6)}; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; width: 200px; `; diff --git a/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx b/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx index 6259bb363..63cd583ec 100644 --- a/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx +++ b/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx @@ -1,3 +1,6 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { zodResolver } from '@hookform/resolvers/zod'; import { useCallback } from 'react'; import { Controller, @@ -5,9 +8,6 @@ import { useFieldArray, useForm, } from 'react-hook-form'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { zodResolver } from '@hookform/resolvers/zod'; import { useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; import { IconCopy } from 'twenty-ui'; @@ -101,15 +101,15 @@ export const InviteTeam = () => { const getPlaceholder = (emailIndex: number) => { if (emailIndex === 0) { - return 'tim@apple.dev'; + return 'tim@apple.com'; } if (emailIndex === 1) { - return 'craig@apple.dev'; + return 'phil@apple.com'; } if (emailIndex === 2) { - return 'mike@apple.dev'; + return 'jony@apple.com'; } - return 'phil@apple.dev'; + return 'craig@apple.com'; }; const copyInviteLink = () => { diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index c01f49991..1a60930c4 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -25,6 +25,7 @@ "@nestjs/graphql": "patch:@nestjs/graphql@12.1.1#./patches/@nestjs+graphql+12.1.1.patch", "@ptc-org/nestjs-query-graphql": "patch:@ptc-org/nestjs-query-graphql@4.2.0#./patches/@ptc-org+nestjs-query-graphql+4.2.0.patch", "@revertdotdev/revert-react": "^0.0.21", + "@sentry/nestjs": "^8.30.0", "cache-manager": "^5.4.0", "cache-manager-redis-yet": "^4.1.2", "class-validator": "patch:class-validator@0.14.0#./patches/class-validator+0.14.0.patch", diff --git a/packages/twenty-server/src/app.module.ts b/packages/twenty-server/src/app.module.ts index ece68c23e..491582085 100644 --- a/packages/twenty-server/src/app.module.ts +++ b/packages/twenty-server/src/app.module.ts @@ -12,6 +12,7 @@ import { existsSync } from 'fs'; import { join } from 'path'; import { YogaDriver, YogaDriverConfig } from '@graphql-yoga/nestjs'; +import { SentryModule } from '@sentry/nestjs/setup'; import { CoreGraphQLApiModule } from 'src/engine/api/graphql/core-graphql-api.module'; import { GraphQLConfigModule } from 'src/engine/api/graphql/graphql-config/graphql-config.module'; @@ -26,8 +27,10 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ import { ModulesModule } from 'src/modules/modules.module'; import { CoreEngineModule } from './engine/core-modules/core-engine.module'; + @Module({ imports: [ + SentryModule.forRoot(), ConfigModule.forRoot({ isGlobal: true, envFilePath: process.env.NODE_ENV === 'test' ? '.env.test' : '.env', diff --git a/packages/twenty-server/src/engine/core-modules/cron/sentry-cron-monitor.decorator.ts b/packages/twenty-server/src/engine/core-modules/cron/sentry-cron-monitor.decorator.ts index 99d1091f9..ad60c242c 100644 --- a/packages/twenty-server/src/engine/core-modules/cron/sentry-cron-monitor.decorator.ts +++ b/packages/twenty-server/src/engine/core-modules/cron/sentry-cron-monitor.decorator.ts @@ -6,15 +6,17 @@ export function SentryCronMonitor(monitorSlug: string, schedule: string) { propertyKey: string, descriptor: PropertyDescriptor, ) { - if (!Sentry.isInitialized()) { - return descriptor; - } - const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { + if (!Sentry.isInitialized()) { + return await originalMethod.apply(this, args); + } + + let checkInId: string | undefined; + try { - Sentry.captureCheckIn( + checkInId = Sentry.captureCheckIn( { monitorSlug, status: 'in_progress', @@ -25,13 +27,14 @@ export function SentryCronMonitor(monitorSlug: string, schedule: string) { value: schedule, }, checkinMargin: 1, - maxRuntime: 1, + maxRuntime: 5, timezone: 'UTC', }, ); const result = await originalMethod.apply(this, args); Sentry.captureCheckIn({ + checkInId, monitorSlug, status: 'ok', }); @@ -39,6 +42,7 @@ export function SentryCronMonitor(monitorSlug: string, schedule: string) { return result; } catch (error) { Sentry.captureCheckIn({ + checkInId, monitorSlug, status: 'error', }); diff --git a/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts index c32701e5b..0f904a858 100644 --- a/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts @@ -1,42 +1,13 @@ import * as Sentry from '@sentry/node'; -import { nodeProfilingIntegration } from '@sentry/profiling-node'; import { ExceptionHandlerOptions } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-options.interface'; import { ExceptionHandlerUser } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-user.interface'; -import { - ExceptionHandlerDriverInterface, - ExceptionHandlerSentryDriverFactoryOptions, -} from 'src/engine/core-modules/exception-handler/interfaces'; -import { WorkspaceCacheKeys } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +import { ExceptionHandlerDriverInterface } from 'src/engine/core-modules/exception-handler/interfaces'; export class ExceptionHandlerSentryDriver implements ExceptionHandlerDriverInterface { - constructor(options: ExceptionHandlerSentryDriverFactoryOptions['options']) { - Sentry.init({ - environment: options.environment, - release: options.release, - dsn: options.dsn, - integrations: [ - // TODO: Redis integration doesn't seem to work - investigate why - Sentry.redisIntegration({ - cachePrefixes: Object.values(WorkspaceCacheKeys).map( - (key) => `engine:${key}:`, - ), - }), - Sentry.httpIntegration(), - Sentry.expressIntegration(), - Sentry.graphqlIntegration(), - Sentry.postgresIntegration(), - nodeProfilingIntegration(), - ], - tracesSampleRate: 0.1, - profilesSampleRate: 0.3, - debug: options.debug, - }); - } - captureExceptions( exceptions: ReadonlyArray, options?: ExceptionHandlerOptions, diff --git a/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts index 6760018bf..6df4e8aa7 100644 --- a/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/exception-handler.module.ts @@ -1,15 +1,15 @@ import { DynamicModule, Global, Module } from '@nestjs/common'; -import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; -import { EXCEPTION_HANDLER_DRIVER } from 'src/engine/core-modules/exception-handler/exception-handler.constants'; -import { - ConfigurableModuleClass, - OPTIONS_TYPE, - ASYNC_OPTIONS_TYPE, -} from 'src/engine/core-modules/exception-handler/exception-handler.module-definition'; import { ExceptionHandlerConsoleDriver } from 'src/engine/core-modules/exception-handler/drivers/console.driver'; import { ExceptionHandlerSentryDriver } from 'src/engine/core-modules/exception-handler/drivers/sentry.driver'; +import { EXCEPTION_HANDLER_DRIVER } from 'src/engine/core-modules/exception-handler/exception-handler.constants'; +import { + ASYNC_OPTIONS_TYPE, + ConfigurableModuleClass, + OPTIONS_TYPE, +} from 'src/engine/core-modules/exception-handler/exception-handler.module-definition'; +import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; @Global() @Module({ @@ -23,7 +23,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass { useValue: options.type === ExceptionHandlerDriver.Console ? new ExceptionHandlerConsoleDriver() - : new ExceptionHandlerSentryDriver(options.options), + : new ExceptionHandlerSentryDriver(), }; const dynamicModule = super.forRoot(options); @@ -45,7 +45,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass { return config.type === ExceptionHandlerDriver.Console ? new ExceptionHandlerConsoleDriver() - : new ExceptionHandlerSentryDriver(config.options); + : new ExceptionHandlerSentryDriver(); }, inject: options.inject || [], }; diff --git a/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/workspace-member.ts b/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/workspace-member.ts index ecb08d56d..ef9c47bd9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/workspace-member.ts +++ b/packages/twenty-server/src/engine/workspace-manager/demo-objects-prefill-data/workspace-member.ts @@ -31,6 +31,7 @@ export const workspaceMemberPrefillData = async ( nameLastName: 'A', locale: 'en', colorScheme: 'Light', + userEmail: 'noah@demo.dev', userId: DEMO_SEED_USER_IDS.NOAH, }, { @@ -39,6 +40,7 @@ export const workspaceMemberPrefillData = async ( nameLastName: 'I', locale: 'en', colorScheme: 'Light', + userEmail: 'hugo@demo.dev', userId: DEMO_SEED_USER_IDS.HUGO, }, { @@ -47,6 +49,7 @@ export const workspaceMemberPrefillData = async ( nameLastName: 'Apple', locale: 'en', colorScheme: 'Light', + userEmail: 'tim@apple.dev', userId: DEMO_SEED_USER_IDS.TIM, }, ]) diff --git a/packages/twenty-server/src/instrument.ts b/packages/twenty-server/src/instrument.ts new file mode 100644 index 000000000..2fdbe0263 --- /dev/null +++ b/packages/twenty-server/src/instrument.ts @@ -0,0 +1,29 @@ +import * as Sentry from '@sentry/nestjs'; +import { nodeProfilingIntegration } from '@sentry/profiling-node'; + +import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; +import { WorkspaceCacheKeys } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; + +if (process.env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry) { + Sentry.init({ + environment: process.env.SENTRY_ENVIRONMENT, + release: process.env.SENTRY_RELEASE, + dsn: process.env.SENTRY_DSN, + integrations: [ + // TODO: Redis integration doesn't seem to work - investigate why + Sentry.redisIntegration({ + cachePrefixes: Object.values(WorkspaceCacheKeys).map( + (key) => `engine:${key}:`, + ), + }), + Sentry.httpIntegration(), + Sentry.expressIntegration(), + Sentry.graphqlIntegration(), + Sentry.postgresIntegration(), + nodeProfilingIntegration(), + ], + tracesSampleRate: 0.1, + profilesSampleRate: 0.3, + debug: process.env.DEBUG_MODE === 'true', + }); +} diff --git a/packages/twenty-server/src/main.ts b/packages/twenty-server/src/main.ts index 55cf4d2a6..b00532747 100644 --- a/packages/twenty-server/src/main.ts +++ b/packages/twenty-server/src/main.ts @@ -2,7 +2,6 @@ import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; -import * as Sentry from '@sentry/node'; import bytes from 'bytes'; import { useContainer } from 'class-validator'; import { graphqlUploadExpress } from 'graphql-upload'; @@ -11,6 +10,7 @@ import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; import { ApplyCorsToExceptions } from 'src/utils/apply-cors-to-exceptions'; import { AppModule } from './app.module'; +import './instrument'; import { settings } from './engine/constants/settings'; import { generateFrontConfig } from './utils/generate-front-config'; @@ -34,10 +34,6 @@ const bootstrap = async () => { // Use our logger app.useLogger(logger); - if (Sentry.isInitialized()) { - Sentry.setupExpressErrorHandler(app); - } - app.useGlobalFilters(new ApplyCorsToExceptions()); // Apply validation pipes globally diff --git a/packages/twenty-server/src/queue-worker/queue-worker.ts b/packages/twenty-server/src/queue-worker/queue-worker.ts index 71cc36ad7..157e098a2 100644 --- a/packages/twenty-server/src/queue-worker/queue-worker.ts +++ b/packages/twenty-server/src/queue-worker/queue-worker.ts @@ -4,6 +4,7 @@ import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handl import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; import { shouldFilterException } from 'src/engine/utils/global-exception-handler.util'; import { QueueWorkerModule } from 'src/queue-worker/queue-worker.module'; +import 'src/instrument'; async function bootstrap() { let exceptionHandlerService: ExceptionHandlerService | undefined; diff --git a/packages/twenty-website/src/content/user-guide/getting-started/what-is-twenty.mdx b/packages/twenty-website/src/content/user-guide/getting-started/what-is-twenty.mdx index a13746ffe..92b5e2d90 100644 --- a/packages/twenty-website/src/content/user-guide/getting-started/what-is-twenty.mdx +++ b/packages/twenty-website/src/content/user-guide/getting-started/what-is-twenty.mdx @@ -49,7 +49,7 @@ Internet connection (Offline mode isn't yet supported). If you wish to try Twenty before creating your own account, go to demo.twenty.com and login with the following credentials: -`email: noah@demo.dev`\ +`email: tim@apple.dev`\ `password: Applecar2025` ## Vision diff --git a/yarn.lock b/yarn.lock index 73c64fd29..293917a22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12196,6 +12196,21 @@ __metadata: languageName: node linkType: hard +"@sentry/nestjs@npm:^8.30.0": + version: 8.30.0 + resolution: "@sentry/nestjs@npm:8.30.0" + dependencies: + "@sentry/core": "npm:8.30.0" + "@sentry/node": "npm:8.30.0" + "@sentry/types": "npm:8.30.0" + "@sentry/utils": "npm:8.30.0" + peerDependencies: + "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 + "@nestjs/core": ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/f0412787f5fac68743ed6e4f4b58fd06a198d3f0b5777dd072784d9c943fbb3725ee82b00838a7f274f4eb51d7776f5a72f80f8cba253f5c30a7649a292f20c0 + languageName: node + linkType: hard + "@sentry/node@npm:8.30.0, @sentry/node@npm:^8": version: 8.30.0 resolution: "@sentry/node@npm:8.30.0" @@ -47464,6 +47479,7 @@ __metadata: "@nx/js": "npm:18.3.3" "@ptc-org/nestjs-query-graphql": "patch:@ptc-org/nestjs-query-graphql@4.2.0#./patches/@ptc-org+nestjs-query-graphql+4.2.0.patch" "@revertdotdev/revert-react": "npm:^0.0.21" + "@sentry/nestjs": "npm:^8.30.0" "@types/lodash.differencewith": "npm:^4.5.9" "@types/lodash.isempty": "npm:^4.4.7" "@types/lodash.isequal": "npm:^4.5.8"