Commit Graph

3308 Commits

Author SHA1 Message Date
Paul Rastoin a6415db775 Refactor workspace migration and validation error types and centralize runner optimistic rendering (#16920)
# Introduction

In this PR we're:
- Refactoring the workspace migration action type introducing grain over
metadata and operation type ( for example operation `create` and
metadata `field` )
- Thanks to above point we can now factorize the runner optimistic
rendering out of each runner actions-handler file using the existing
into the generic one ( -3200 lines of code here )
- Still thanks to action type refactor we're able to dynamically compose
the response error type only send data when there's here. No more static
counter and static summary error message. This way we won't have to re
run snapshot every time we add a new entity to the engine ( huge
snapshot diff here )

## Noticeable points:
- We introduce an index update action to avoid any complex typing for
not having one or a tuple of actions instead. Now the drop and insert
logic is directly inferred from the update action handler instead of
being two action ( delete index and create index )

## TODO
- [x] Define base actions types
- [x] Migrate all actions to action type and metadata name pattern (
base actions )
- [x] Refactor flat entity validation type to embed metadata name
- [x] Refactor optimistic rendering within runner
- [x] Refactor legacy cache invalidation switch
- [x] Refactor response error format ( dynamic counter again + no empty
entries )
- [x] Try factorizing and removing redundant nor unused type declaration
in metadata actions type intermediary files
- [x] Adapt front to new response error format

## Remarks
- ~~Should create an issue for generic replace flat entity in related
flat entity maps~~ overkill
- Should create an issue for oneToMany foreignKey being nullable not
always cascade delete optimistic rendering edge case to either docs or
fix it in delete flat entity and related entity ( re-code the pg
cascading behavior )
- We could also factorize the builder to only implement validators and
not the intermediary file
2026-01-05 10:17:16 +01:00
Paul Rastoin 9b38254256 Refactor runner dynamic cache invalidation (#16913)
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/1792
Refactoring the cache to invalidate to be more precise and prevent any
corrupted cache occurrences

## Next
The load dependency cache from the workspace migration, that's not
optimal it should be smart enough to infer it from the actions
themselves. For the moment their definition isn't granular enough and
inferring such info would be very dirty
2026-01-03 02:51:07 +01:00
Paul Rastoin 42c9ae1ebc Centralize metadata relations constant + simplification (#16901)
# Introduction
As we introduced a new grain on relation extraction thanks to low level
`SyncableEntity` and `WorkspaceRelatedEntity` we're able to strictly
typesafe extract metadata entity

The new constant centralizes both many to one and one to many constants
metadata entity constants in a more strictly typesafe way. Remains only
the flatEntityForeignKey aggregator which has to be chosen manually
across all available targeted flat entity ids properties
2026-01-02 16:49:06 +01:00
github-actions[bot] 3cab1bf80b i18n - translations (#16909)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-02 15:36:03 +01:00
Félix Malfait 21ff42074d feat: implement skills system for AI agents (#16865)
## Summary
This PR introduces a Skills system for AI agents, inspired by the [Agent
Skills specification](https://agentskills.io/specification).

## Changes

### Backend
- **SkillEntity**: New database entity with migration for storing skills
- **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators,
and action handlers following the v2 flat entity pattern
- **Standard Skills**: Pre-defined skills (workflow-building,
data-manipulation, dashboard-building, metadata-building, research,
code-interpreter, xlsx, pdf, docx, pptx)
- **GraphQL API**: CRUD operations for skills with proper guards and
permissions
- **Workspace Cache**: Integrated skills into the workspace cache system

### Frontend  
- **Skills Table**: Searchable table in AI settings showing all skills
- **Skill Form**: Create/edit page with Label (primary), Description,
and Content (markdown editor)
- **API Name**: Following existing patterns, name is derived from label
with advanced settings toggle for custom API names
- **Standard vs Custom**: Standard skills are read-only, custom skills
can be edited/deleted

## Key Design Decisions
- Skills are stored in the database (Salesforce-like approach) rather
than files
- Name is derived from Label by default (isLabelSyncedWithName pattern)
- Skills reference functions/files via @ mentions in markdown content
rather than explicit relations
- Standard skills are synced from code, custom skills are created via UI

## Screenshots
Skills table and form UI follow existing settings patterns.

## Testing
- [x] Lint passes
- [x] Typecheck passes
- [ ] CI tests
2026-01-02 14:22:01 +00:00
github-actions[bot] 2a3fd788ae i18n - translations (#16908)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-02 14:24:15 +01:00
Etienne 300738e8cb Fix database event emission (#16759)
Fixes https://github.com/twentyhq/private-issues/issues/395


When calling `removeUserFromWorkspaceAndPotentiallyDeleteWorkspace` from
`user.service`,
```
        await workspaceMemberRepository.delete({
          userId: userWorkspace.userId,
        });
```
related database event is not emitted.

Same issue with update has been fixed
[here](https://github.com/twentyhq/twenty/pull/13287/changes)

The database event is not emitted because `await
eventSelectQueryBuilder.getOne()` in `workspace-delete-query-builder`
returns `null`. This happens because the `selectQueryBuilder` has no
entity—the database request is sent and the raw result is not `null`,
but the entity is `null`, causing the final result to be null.

It can be fixed the same way update has been fixed, updating the
`workspace-entity-manager`
- Pros : consistant with update but we should not forget to fix
softDelete and restore
- Cons : `workspace-entity-manager` is a copy of typeORM logic +
permission injection. Should it be more ?
 
Alternatively (as featured in this PR), it can be fixed by updating
computeEventSelectQueryBuilder, inspired by TypeORM's logic in
typeorm/query-builder/QueryBuilder.js at line 67.
- Pros : it fit with typeORM logic + It fixes all repository operations
2026-01-02 14:23:47 +01:00
Etienne ecd41fc9cb Reduce complexity on groupBy query (#16803)
fixes https://github.com/twentyhq/core-team-issues/issues/2009
2026-01-02 14:19:40 +01:00
Paul Rastoin 98a9ae2a0e Migrate view filter group to v2 (#16876)
## Introduction
On the side hanlded PR with a // agents on another repo
Made several iterations to fix behavior and direction
Find below auto-generated PR description

closes https://github.com/twentyhq/core-team-issues/issues/2037

Created generic tooling for entity circular dep checking, will be useful
for permissions validation too @Weiko


## Migrate `viewFilterGroup` entity to v2 flat architecture

### Summary
Migrates the `viewFilterGroup` entity from v1 to the v2 flat entity
architecture, following the established patterns for other v2 entities
like `viewFilter`, `view`, and `viewField`.

### Changes

**Types & Constants**
- Added `FlatViewFilterGroup` and `FlatViewFilterGroupMaps` types
- Added editable properties constant for `viewFilterGroup`
- Registered `viewFilterGroup` in `ALL_METADATA_NAME`,
`ALL_METADATA_RELATION_PROPERTIES`,
`ALL_METADATA_MANY_TO_ONE_RELATIONS`, and related constants

**Cache Service**
- Created `WorkspaceFlatViewFilterGroupMapCacheService` with proper
relation loading for `viewFilters` and `childViewFilterGroups`
- Updated `WorkspaceFlatViewMapCacheService` to load `viewFilterGroups`
relation

**Builder & Validator**
- Created `WorkspaceMigrationV2ViewFilterGroupActionsBuilderService`
- Created `FlatViewFilterGroupValidatorService` with creation, update,
and deletion validation
- Integrated validation into the orchestrator service (runs before
`viewFilter` validation)

**Action Handlers**
- Created create, update, and delete action handlers for
`viewFilterGroup`

**Service Migration**
- Rewrote `ViewFilterGroupService` to use v2 migration pattern with
`WorkspaceMigrationValidateBuildAndRunService`
- Created utility functions for transforming DTOs to flat entities

**Database Migration**
- Added migration to make `parentViewFilterGroupId` foreign key
deferrable (handles self-referential parent/child insertions)

**ViewFilter Integration**
- Added `viewFilterGroupId` validation in
`FlatViewFilterValidatorService`
- Updated `viewFilter` many-to-one relations to include
`viewFilterGroup`

**Tests**
- Added integration tests for successful creation, update, deletion, and
destruction
- Added failing test cases for non-existent entities and invalid
references
- Added failing test for `viewFilter` creation with non-existent
`viewFilterGroupId`

### Breaking Changes
None - existing API contracts are preserved.
2026-01-02 14:16:01 +01:00
github-actions[bot] 0adbe439d5 i18n - translations (#16895)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-01-01 15:35:33 +01:00
Paul Rastoin 1d9eb07ee0 Relation field metadata strict validation (#16890)
# Introduction

Now validating relation related object and field foreignkey integrity
2026-01-01 15:09:48 +01:00
Abdul Rahman dff5e3cd7b feat: Add If/Else node (#16833)
Closes [#1265](https://github.com/twentyhq/core-team-issues/issues/1265)
2026-01-01 14:05:56 +00:00
Weiko 1d124a7a77 Enabled RLS seed dev (#16884)
And remove IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Removes deprecated feature flag and enables RLS predicates in dev**
> 
> - Deletes `IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED` from server enum
and frontend generated GraphQL types
> - Updates dev seeder to enable
`IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED` instead of the removed flag
> - Adjusts tests and mocked `GlobalWorkspaceDataSource` defaults to
drop the removed flag
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7f4d5a401a42e46dfcc576161d4f8167544f4453. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-01-01 13:55:46 +01:00
github-actions[bot] 9a5c2e4cea i18n - translations (#16882)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-31 16:34:31 +01:00
Weiko 15b21570ae Row level permissions - POC 1 (#16599)
## Context
This PR adds the core structure for RLS implementation:
- RLS data model
- RLS service layer
- RLS WorkspaceMigration and Syncable Entity + cache + Validations
- RLS resolver layer
- ORM layer with RLS Predicate to ORM WHERE clause conversion with
workspaceMember record transposition

Tests are missing though

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Establishes core row-level permissions infrastructure and enforcement
across the stack.
> 
> - Backend: new `rowLevelPermissionPredicate` and
`rowLevelPermissionPredicateGroup` entities, TypeORM migration, feature
flag `IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED`, flat-entity
maps/cache wiring, services and GraphQL resolvers for CRUD, and
inclusion of `workspaceMember` in auth context
> - ORM: applies row-level permission predicates to SELECT, DELETE, and
SOFT DELETE query builders; propagates context through
GlobalWorkspaceOrmManager/EntityManager
> - GraphQL: generated schema/types/queries/mutations for
creating/updating/deleting/fetching predicates and groups
> - Frontend: settings page adds a gated "Record-level" section
(placeholder) and metadata error handler labels for new entities
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fe955cc4588a92157afa6795fb574189a4be1e93. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-12-31 16:15:17 +01:00
Paul Rastoin d6acef24d4 Fix timeline activities display (#16880)
Fixes https://github.com/twentyhq/twenty/issues/16809

# Introduction
Front was expected timeline activities field to be relation and not
morph relation

Also fixed the new v2 workspace creation that had a bug in the
createStandardRelation util ( it's not in production yet so everything's
fine )
2025-12-31 13:42:56 +00:00
Abdul Rahman 229916def2 Fix: Change opportunity kanban view aggregate operation from MIN to SUM (#16879)
Closes #16806
2025-12-31 12:58:20 +00:00
Paul Rastoin eeb91d9a96 [DO_NOT_RELEASE_MAIN_UNTIL_MERGED] Prevent migration failure due to workspace orphan metadata rows (#16863)
# Introduction
The `AddWorkspaceForeignKeys1767002571103` migration would fail when
released in production right now, as `foreignKey` be applicable as
there's a lof of orphan entries in database

As a workaround in order not to block any patch release we're
fallbacking the migration using save point and an upgrade command that
will attempt to apply the `foreignKey` on every workspace upgrade until
it succeed

We should keep in mind that any new fresh self installation will have
the foreignKey double checked that it would not implies regression on
workspace deletion using the integration tests

## Cleaning upgrade command
We won't implement the cleaning command in this PR yet either will I as
discussed with @Weiko someone else might be taking the subject starting
next week

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Strengthens workspace data integrity and makes the FK migration
resilient.
> 
> - Adds `upgrade:1-16:add-workspace-foreign-keys-migration` command to
apply `workspaceId` FKs once per run; wires into
`V1_16_UpgradeVersionCommandModule` and 1.16 upgrade sequence
> - Refactors migration `1767002571103` to use
`addWorkspaceForeignKeysQueries` util and wrap in a savepoint,
swallowing errors to avoid blocking releases
> - Extracts FK DDL into
`utils/1767002571103-addWorkspaceForeignKeys.util` for reuse by command
and migration
> - Removes duplicate `workspaceId` columns from entities (e.g.,
`cronTrigger`, `databaseEventTrigger`, `indexMetadata`,
`objectMetadata`, `roleTarget`, `role`, `serverlessFunction`) relying on
`SyncableEntity`; keeps indexes/relations
> - Marks legacy delete paths as deprecated; temporarily extends
`WorkspaceManagerService.delete` to also delete `serverlessFunction` by
`workspaceId`
> - Updates wiring to inject `ServerlessFunctionEntity` repository in
`workspace-manager` module/service and corresponding unit test
> - Extends integration tests and adds GraphQL helpers to create
serverless functions and triggers; verifies cascade deletion of related
metadata on workspace removal
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6805bf5d1b32828b4bb1e9f130bfe6e478f66aee. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-12-31 11:52:21 +00:00
Félix Malfait f8fa709abf refactor: Migrate CRUD services to use Common API (#16869)
This PR migrates the workflow CRUD services to use the Common API
(CommonQueryRunners) instead of directly accessing TwentyORM.

## Changes

- Created CommonApiContextBuilderService to build context for Common API
- Migrated CreateRecordService to use CommonCreateOneQueryRunnerService
- Migrated UpdateRecordService to use CommonUpdateOneQueryRunnerService
- Migrated DeleteRecordService to use CommonDeleteOneQueryRunnerService
- Migrated FindRecordsService to use CommonFindManyQueryRunnerService
- Migrated UpsertRecordService to use Common API with upsert flag
- Removed unused get-selected-columns-from-restricted-fields.util.ts
- Updated module dependencies

## Benefits

- Consistent permission checking via Common API
- Query hooks (before/after execution)
- Automatic input transformation
- Same behavior as REST/GraphQL APIs
- Reduced code duplication
2025-12-31 10:24:50 +01:00
Félix Malfait 009e7e05f2 feat(workflow): use authContext in CRUD services for Common API migration (#16857)
## Summary

This PR migrates workflow CRUD operations to properly use the Common API
layer's authentication context, addressing the issues from the reverted
PR #15875.

The original PR was reverted because the Common API required passing
either a User or an API Key for authentication, which was problematic
for workflows. Since then, the "Application" concept was introduced in
the Common API layer, allowing for token injection in serverless
functions.

This PR leverages the "Twenty Standard Application" concept for
non-manual workflow triggers, providing a clean authentication path
without the issues of user impersonation.

## Changes

### Core Infrastructure
- **RecordCrudExecutionContext**: Replace `workspaceId` with full
`authContext`
- **WorkflowExecutionContext**: Add `authContext` field to carry
authentication info
- **ToolGeneratorContext/ToolSpecification**: Add optional `authContext`
support for tool generation

### Authentication Flow
- **WorkflowExecutionContextService**: Build appropriate auth context
based on trigger type:
- **Manual triggers**: Use user's workspace auth context with their role
permissions
- **Non-manual triggers**: Use Twenty Standard Application auth context
(bypasses permission checks or uses default serverless function role)
- **ApplicationService**: Add `findTwentyStandardApplicationOrThrow`
method to retrieve the system application
- **UserWorkspaceService**: Make relations configurable in
`getUserWorkspaceForUserOrThrow` to load only what's needed

### CRUD Services Migration
All 5 record CRUD services now receive `authContext` instead of
`workspaceId`:
- `CreateRecordService`
- `UpdateRecordService`
- `DeleteRecordService`
- `FindRecordsService`
- `UpsertRecordService`

### Workflow Actions
All record CRUD workflow actions pass `executionContext.authContext` to
the services:
- `CreateRecordWorkflowAction`
- `UpdateRecordWorkflowAction`
- `DeleteRecordWorkflowAction`
- `FindRecordsWorkflowAction`
- `UpsertRecordWorkflowAction`

### AI Agent Integration
- AI agent workflow action passes auth context to agent executor
- Tool provider and MCP protocol service support auth context
propagation

## Benefits
-  Proper authentication for workflow CRUD operations via Common API
-  Non-manual triggers use system application context (no user
impersonation issues)
-  Manual triggers preserve user permissions correctly
-  Foundation for better permission handling in automated workflows
-  Cleaner separation between user-initiated and system-initiated
operations

## Related
- Reverted PR: #15875
2025-12-30 21:36:15 +01:00
Paul Rastoin 1128331cc1 Fix field item type tag (#16860)
# Introduction
This has been fixed on main already 1 hour ago, this PR now only passes
the field application id instead of the object applicationId that could
be different for example when creating a custom field on a standard
object

For the moment not introducing any logic around application integrity
directly and still relying on the isCustom and standardId definition
This will have to be refactored once we deprecate the standardId
2025-12-30 15:02:07 +00:00
Weiko ab95437065 Create query runner context from request context instead of schema builder internal context (#16858)
## Context
This PR refactors how authentication context is handled in the GraphQL
resolver layer. Previously, the auth context was baked into the schema
builder context at schema creation time. Now, the auth context is
extracted from the request at query execution time, enabling better
schema caching.

## Implementation
- Removed user-specific data from schema cache key: The schema cache key
no longer includes userId and apiKeyId, allowing the same schema to be
shared across all users in a workspace
- Cache key simplified from
${workspaceId}-${userId}-${apiKeyId}-${url}-${cacheVersion} to
${workspaceId}-${url}-${cacheVersion}
- Removed authContext from WorkspaceSchemaBuilderContext: The schema
builder context is now purely about object/field metadata, not about who
is accessing it
- Created createQueryRunnerContext utility: A new helper that combines
the schema builder context with the auth context from the actual request
- Updated all resolver factories: All 15 resolver factories now use
createQueryRunnerContext to build the full context needed by query
runners at execution time

## Benefits
- Improved cache efficiency: GraphQL schemas are now cached per
workspace rather than per user, significantly reducing memory usage and
schema regeneration
- Cleaner separation of concerns: Schema building (metadata-driven) is
now separate from query execution (auth-driven)
- Fresh auth context: Auth context is always retrieved from the current
request, ensuring it reflects the latest state
- Reduced complexity: Simplified the schema creation path by removing
unnecessary auth checks

## Next steps
- Introduce a hash in redis and expose it in the req object so the yoga
patch can access the hash and use it during its own cache key
computation. This way we will be able to invalidate the local cache in
yoga graphql schema generation without restarting pods.
2025-12-30 15:16:57 +01:00
Paul Rastoin e3ffdb0c2b [BREAKING_CHANGE_NESTED_WORKSPACE]Refactor FlatEntity typing in aim of introducing UniversalFlatEntity (#16701)
# Introduction
Added a `WorkspaceRelated` and `AllNonWorkspaceRelatedEntity` to
simplify the `FlatEntityFrom` that now do not expect a string literal to
omit and itself builds the related many to one entities foreign key
aggregators

We now have the type grain over relation to syncable or just workspace
related entities

Added a migrations that sets the fk on missing entities

## Next
In upcoming PR we will be able to introduce such below type
```ts
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/remove-suffix.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';

export type UniversalFlatEntityFrom<TEntity extends SyncableEntity> = Omit<
  TEntity,
  | `${ExtractEntityManyToOneEntityRelationProperties<TEntity> & string}Id`
  | ExtractEntityRelatedEntityProperties<TEntity>
  | 'application'
  | 'workspaceId'
  | 'applicationId'
  | keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
> &
  CastRecordTypeOrmDatePropertiesToString<TEntity> & {
    [P in ExtractEntityManyToOneEntityRelationProperties<TEntity> &
      string as `${RemoveSuffix<P, 's'>}UniversalIdentifier`]: string;
  } & {
    [P in ExtractEntityOneToManyEntityRelationProperties<
      TEntity,
      SyncableEntity
    > &
      string as `${RemoveSuffix<P, 's'>}UniversalIdentifiers`]: string[];
  };

```
2025-12-30 13:47:02 +00:00
Abdul Rahman 720348c583 Add upgrade commands to backfill updatedBy field and view fields (#16845)
---
prastoin edit
## Introduction
As we're deprecating the sync metadata we cannot rely on it anymore in
order to create the new standard updatedBy field
In this PR we introduce a command that will backfill the dynamic field
on both workspace standard and custom objects, for standard object it
will create a standard fields ( twenty-standard app scoped ) and for
custom one it will create a custom field ( worksapce-custom-app scoped )

## Testing method
Checkout `1.14.0` `yarn` and `npx nx database:reset twenty-server`
checkout PR and `yarn` `npx nx build twenty-server` and `yarn
database:migrate:prod` finally running the command

#### Before
<img width="2244" height="1464" alt="image"
src="https://github.com/user-attachments/assets/2fb866cd-13b8-4152-99b8-1fcc813b1d46"
/>

#### After
<img width="2244" height="1464" alt="image"
src="https://github.com/user-attachments/assets/b836ac06-c7b8-4add-bee1-bc1963418f29"
/>


#### Logs
```ts
[Nest] 20678  - 12/30/2025, 1:47:35 PM     LOG [BackfillUpdatedByFieldCommand] Found 12 objects that need updatedBy field
[EntityBuilder fieldMetadata] matrix computation: 3.994ms
[EntityBuilder fieldMetadata] creation validation: 1.822ms
[EntityBuilder fieldMetadata] deletion validation: 0.016ms
[EntityBuilder fieldMetadata] update validation: 0.009ms
[EntityBuilder fieldMetadata] entity processing: 6.098ms
[EntityBuilder fieldMetadata] validateAndBuild: 10.217ms
[EntityBuilder index] matrix computation: 0.857ms
[EntityBuilder index] creation validation: 0.003ms
[EntityBuilder index] deletion validation: 0.017ms
[EntityBuilder index] update validation: 0.008ms
[EntityBuilder index] entity processing: 4.721ms
[EntityBuilder index] validateAndBuild: 5.632ms
[Runner] Initial cache retrieval: 0.07ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 7.69ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 10.531ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 5.354ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 6.134ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 2.507ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 3.745ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.113ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 1.585ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.223ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.221ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 5.375ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 5.943ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.363ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 1.997ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 2.03ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.982ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 0.88ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.977ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 9.638ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 11.297ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.581ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.248ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 0.847ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 1.732ms
[Runner] Transaction execution: 82.555ms
[Runner] Cache invalidation flatFieldMetadataMaps,flatIndexMaps,flatObjectMetadataMaps: 89.247ms
[Runner] Total execution: 171.963ms
[Nest] 20678  - 12/30/2025, 1:47:35 PM     LOG [BackfillUpdatedByFieldCommand] Successfully backfilled updatedBy field for 12 objects in workspace 20202020-1c25-4d02-bf25-6aeccf7ea419
[Nest] 20678  - 12/30/2025, 1:47:35 PM     LOG [BackfillUpdatedByFieldCommand] Running command on workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db 2/2
[Nest] 20678  - 12/30/2025, 1:47:35 PM     LOG [BackfillUpdatedByFieldCommand] Starting backfill of updatedBy field for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db
[Nest] 20678  - 12/30/2025, 1:47:35 PM     LOG [BackfillUpdatedByFieldCommand] Found 10 objects that need updatedBy field
[EntityBuilder fieldMetadata] matrix computation: 3.224ms
[EntityBuilder fieldMetadata] creation validation: 0.969ms
[EntityBuilder fieldMetadata] deletion validation: 0.014ms
[EntityBuilder fieldMetadata] update validation: 0.002ms
[EntityBuilder fieldMetadata] entity processing: 5.149ms
[EntityBuilder fieldMetadata] validateAndBuild: 8.472ms
[EntityBuilder index] matrix computation: 0.785ms
[EntityBuilder index] creation validation: 0.002ms
[EntityBuilder index] deletion validation: 0.014ms
[EntityBuilder index] update validation: 0.002ms
[EntityBuilder index] entity processing: 3.818ms
[EntityBuilder index] validateAndBuild: 4.637ms
[Runner] Initial cache retrieval: 0.05ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 2.964ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 3.902ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.502ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.039ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 2.673ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 3.403ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.143ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.123ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 2.033ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.954ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.826ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.903ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.495ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.106ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 14.014ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 14.373ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 1.669ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 2.48ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForMetadata: 0.62ms
[BaseWorkspaceMigrationRunnerActionHandlerService] create_field executeForWorkspaceSchema: 1.048ms
[Runner] Transaction execution: 73.222ms
[Runner] Cache invalidation flatFieldMetadataMaps,flatIndexMaps,flatObjectMetadataMaps: 37.703ms
[Runner] Total execution: 111.071ms
```
---

---------

Co-authored-by: prastoin <paul@twenty.com>
2025-12-30 14:05:26 +01:00
Thomas Trompette a2827494a1 Support actor filtering in workflows (#16783)
Fixes https://github.com/twentyhq/twenty/issues/15782

Find records
<img width="422" height="423" alt="Capture d’écran 2025-12-23 à 16 23
49"
src="https://github.com/user-attachments/assets/5856de06-d608-46f6-97a6-8f382f355a44"
/>

Filters
<img width="422" height="271" alt="Capture d’écran 2025-12-23 à 16 23
32"
src="https://github.com/user-attachments/assets/fcc10b6e-f1ec-497a-8593-d599164d4e4b"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds full support for filtering on `ACTOR` composite fields across UI
and server.
> 
> - Front-end: handles `ACTOR` in advanced filters and workflow filters;
`source` uses multi-select options from `FieldActorSource`,
`workspaceMemberId` uses `FormSingleRecordPicker`, others default to
text
> - Updates operand mapping for `ACTOR` (`source`→`SELECT`,
`workspaceMemberId`→`RELATION`, else `TEXT`)
> - Server: adds `ACTOR` evaluation with subfield-specific logic
(delegates to select/relation/text evaluators)
> - Simplifies `AdvancedFilterFieldSelectMenu` by removing
workflow-specific field filtering
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7a8164cd2d4cad321ff1009bc7d1c4295a6672ca. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
2025-12-30 09:34:08 +00:00
github-actions[bot] d39f105a03 i18n - translations (#16849)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-30 10:21:48 +01:00
Marie 19c9f957b1 Improve userFriendlyMessage devX (#16815)
Two challenges with error messages
- always provide a useful/meaningful error message for the end user
instead of the generic one. eg: show "Wrong password" and not "An error
occured"
- avoid technical details unless error regards a technical feature. eg:
show "An error occured" and not "Invalid post-hook payload."; but do
show "Invalid issuer URL." as it occurs while configuring SSO

What this PR does
- Make userFriendlyMessage mandatory for widely used
GraphqlQueryRunnerException and CommonQueryRunnerException, so that
developers are forced to ask themselves what the error message should
be, and as it contains very wide error codes (eg: "Bad request") which
should not be mapped to just one default message
- Keep userFriendlyMessage optional for service-specific exceptions (eg:
workflowStepExecutorException), but convert the error code to
userFriendlyMessage mapper to a switch case function with a typecheck
ensuring that all codes are mapped to a message. These default messages
are still overridable where they are thrown.
2025-12-30 09:08:43 +00:00
github-actions[bot] 418377b867 i18n - translations (#16847)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-30 09:51:57 +01:00
ANKIT VARSHNEY 8d130908c2 Allow user to update profile picture (#16812)
Fixes [#16805](https://github.com/twentyhq/twenty/issues/16805)

User was not able to update his own profile picture it showed permisson
error while doing so.

Updated permission so that user is able to edit profile picture

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Ensures profile picture uploads succeed only with appropriate
permissions and clearer errors.
> 
> - Tightens `UploadProfilePicturePermissionGuard` to handle missing
`workspace`, allow during workspace creation, and permit uploads when
user has `WORKSPACE_MEMBERS` or `PROFILE_INFORMATION` settings;
otherwise throws a `PermissionsException` with a user-friendly message
> - In `user-workspace.resolver.ts`, validates the upload result and
throws an error if no files were returned
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3766bac15e340bdd467de3a9d923dbd697aab3db. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
2025-12-30 09:46:10 +01:00
Félix Malfait 3a673f99ba Disable updated By 2 (#16840)
Prepare for release, will re-enable them
2025-12-29 18:57:17 +01:00
Félix Malfait b56dcd8c22 Temporarily disable updatedBy (#16839)
To avoid breaking workflows during release
2025-12-29 17:06:16 +01:00
Paul Rastoin 4135a6473a [BREAKING_CHANGE_DASHBOARDS][DASHBOARD_CACHE_FLUSH_REQUIRED] Refactor page layout widget configuration type (#16671)
# Introduction
In this pull-request we're refactoring the page layout widget
configuration entity to be containing its discriminated key simplifying
underlying code and maintainability
- Made the configuration and title non nullable
- Introduced a generic predicate for the `widgetConfigurationType`
- Upgraded command to remove `graphType` and insert new
`configurationType` to existing entries
- Migrated frontend to new type system

---------

Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
2025-12-29 15:46:59 +01:00
Marie bb18f78f1b [Fix] Fix NaN position in notes resulting in not being able to create notes (#16818)
Following [discord
thread](https://discord.com/channels/1130383047699738754/1453910755387899996/1453910755387899996),
reproductible on twenty-eng


https://github.com/user-attachments/assets/ae7f363d-87e1-44fa-8fe2-ee78412d62a7

Records positions are computed at record creation, depending on the
position arg from the request, being equal to `last`, `first`, or not
present.
When being equal to last, as it is done when creating a note from the
product, the position is calculated using `.maximum()` function which
uses postgres' MAX function. If there is a `NaN` value among the list,
the MAX will return `NaN` too. So if for some reason there is a NaN
somewhere in the position column, all subsequent records being created
with last position argument will be created with NaN value. Until [this
PR](https://github.com/twentyhq/twenty/pull/16630), where we introduced
a validation on position at record creation which throws when NaN is
trying to be introduced as a position, this was going silent. (fyi
@etiennejouan , not on you at all but for info)

Looking into twenty-eng workspace, I found note records with NaN
position dating back to august 2025, making it hard to understand and
debug why they were introduced with NaN position. So I did not find the
real root cause, but I suggest to
- update record-position.service to fix the issue for subsequent records
that go through this service (which is what is currently broken)
- run a command to fix the existing records with NaN position for Notes,
as it is where the issue happened for both the user reporting the issue
on discord, and us on twenty-eng. So hopefully the problem was limited
to Notes
2025-12-29 13:55:20 +01:00
github-actions[bot] ee0e2ed854 i18n - translations (#16832)
Created by Github action

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-29 11:01:07 +01:00
github-actions[bot] 343b74666f i18n - translations (#16831)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-29 10:22:32 +01:00
Abdul Rahman 01e0502fcf feat: add updatedBy field to track last record modifier (#16807)
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Implements last-modifier tracking and unified actor injection.
> 
> - New `ActorFromAuthContextService` replaces createdBy-only logic;
pre-query hooks now inject both `createdBy` and `updatedBy` on create
and `updatedBy` on update
> - Add `updatedBy` standard field to core/custom objects (e.g.,
`person`, `company`, `task`, `note`, `attachment`, `dashboard`,
`workflow`, `workflowRun`) with IDs, metadata builders, and ORM entity
fields
> - Record CRUD: `create` now sets both `createdBy` and `updatedBy`;
`update` sets `updatedBy`; workflow actions use a shared workflow actor
builder; REST base handler uses the new actor service
> - Seeder data and snapshots updated to include `updatedBy`; GraphQL
result formatting adds defaults/handling for empty composite fields
(incl. actor)
> - Frontend `useUpdateOneRecord` upserts returned record into the local
store after mutation
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1f283778e9cb28a35bf84632a1a2997250bb3131. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-12-29 09:05:17 +00:00
Félix Malfait 1b6a0cd05a fix(billing): redirect to Stripe payment method setup when subscribing without payment method (#16827)
## Summary

When users click 'Subscribe Now' during a trial period without a payment
method configured, they previously saw an unhelpful error message: "No
payment method found. Please update your billing details."

This PR improves the UX by redirecting users directly to Stripe's
billing portal payment method update flow, where they can add their
payment information. After adding a payment method, users are redirected
back to the billing settings page and can click 'Subscribe Now' again to
successfully activate their subscription.

## Changes

### Backend
- **stripe-billing-portal.service.ts**: Added
`createBillingPortalSessionForPaymentMethodUpdate` method that creates a
Stripe billing portal session with `flow_data.type:
'payment_method_update'`
- **billing-portal.workspace-service.ts**: Added
`computeBillingPortalSessionURLForPaymentMethodUpdate` method to build
the portal URL
- **billing-end-trial-period.output.ts**: Added optional
`billingPortalUrl` field to the GraphQL output DTO
- **billing-subscription.service.ts**: Modified `endTrialPeriod` to
return `stripeCustomerId` when no payment method exists
- **billing.resolver.ts**: Updated resolver to orchestrate billing
portal URL generation when no payment method

### Frontend
- **useEndSubscriptionTrialPeriod.ts**: Redirect to billing portal URL
instead of showing error snackbar
- **endSubscriptionTrialPeriod.ts**: Added `billingPortalUrl` to
mutation response fields

## User Flow

1. User clicks "Subscribe Now" during trial
2. Backend checks for payment method
3. If no payment method → redirect to Stripe billing portal payment
method update page
4. User adds payment method in Stripe
5. User is redirected back to `/settings/billing`
6. User clicks "Subscribe Now" again to activate subscription
2025-12-29 05:39:53 +01:00
Félix Malfait 7021a51747 feat(billing): implement credit rollover from one billing period to another (#16802)
## Summary
This PR implements credit rollover functionality for billing, allowing
unused credits from one billing period to carry over to the next (capped
at the current period's subscription tier cap).

## Changes

### New Services
- **StripeCreditGrantService**: Interacts with Stripe's Billing Credits
API to create credit grants, retrieve customer credit balances, and void
grants
- **BillingCreditRolloverService**: Contains the rollover logic -
calculates unused credits and creates new grants for the next period
- **BillingWebhookCreditGrantService**: Handles
`billing.credit_grant.created` and `billing.credit_grant.updated`
webhooks to update billing alerts

### Modified Services
- **StripeBillingAlertService**: Updated to include credit balance when
calculating usage threshold alerts
- **BillingUsageService**: Returns rollover credits to the frontend for
display
- **BillingSubscriptionService**: Queries credit balance when creating
billing alerts
- **BillingWebhookInvoiceService**: Triggers rollover processing on
`invoice.finalized` webhook

### Frontend
- Updated `SettingsBillingCreditsSection` to display base credits,
rollover credits, and total available
- Updated GraphQL query to fetch new `rolloverCredits` and
`totalGrantedCredits` fields

### Stripe SDK Upgrade
- Upgraded from v17.3.1 to v19.3.1 to get proper types for
billing.credit_grant events
- Fixed breaking changes: invoice.subscription path, subscription period
fields location, removed properties

## How it works

1. When `invoice.finalized` webhook is received for
`subscription_cycle`, the system:
   - Calculates usage from the previous period
   - Determines unused credits (tier cap - usage)
   - Caps rollover at current tier cap
   - Creates a Stripe credit grant with expiration at end of new period

2. When credit grants are created/updated/voided:
   - Billing alerts are recreated with the updated credit balance

3. The UI displays:
   - Base credits (from subscription tier)
   - Rollover credits (from previous periods)
   - Total available credits

## Edge Cases Handled
- Credit grant voided: `billing.credit_grant.updated` webhook triggers
alert update
- Credit grant expired: Stripe's `creditBalanceSummary` API excludes
expired grants
- No unused credits: Rollover service skips grant creation
- Customer ID as object: Controller extracts `.id` from expanded
customer
2025-12-28 15:42:18 +01:00
Félix Malfait 265345fd69 fix: prevent infinite loop when metadata version cache is empty (#16816)
## Context

After flushing the Redis cache (e.g., via `cache:flush` command), users
get stuck in an infinite loop showing "Your workspace has been updated
with a new data model. Please refresh the page." - refreshing the page
doesn't help.

## Root Cause

When the cache is flushed, `getMetadataVersion()` returns `undefined`.
The version comparison in the error handler then becomes:

```typescript
requestMetadataVersion !== `${currentMetadataVersion}`
// Evaluates to: "5" !== "undefined" → always true
```

This triggers the schema mismatch error on every request, even after
page refresh.

## History

| Date | Commit | What Happened |
|------|--------|---------------|
| Aug 2024 | #6691 (`17a1760afd`) | Introduced the
`${currentMetadataVersion}` template literal that converts `undefined`
to the string `"undefined"` |
| Dec 2025 | #16536 (`0035fc1145`) | Removed the safety check that would
throw an early error when cache is empty, allowing `undefined` to
propagate |

## Fix

Add `isDefined(currentMetadataVersion)` check before comparing versions.
If the server doesn't have a cached version, we shouldn't treat it as a
mismatch - the schema factory already handles populating the cache when
it actually needs the version.

```typescript
if (
  requestMetadataVersion &&
  isDefined(currentMetadataVersion) &&  // ← Added this check
  requestMetadataVersion !== `${currentMetadataVersion}`
) {
```

## Testing

1. Flush the cache: `npx nx run twenty-server:command cache:flush`
2. Refresh the page
3. Verify no infinite loop occurs and app loads normally
2025-12-27 07:09:44 +01:00
github-actions[bot] 28dc3e470e i18n - translations (#16814)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-26 09:39:27 +01:00
github-actions[bot] 874e4e4666 i18n - translations (#16813)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-26 09:22:02 +01:00
Félix Malfait 6a4974e285 feat: enforce credit limits via Stripe alerts for all billing scenarios (#16801)
## Summary

This PR ensures usage alerts are created for all billing scenarios.

## Background

Per [Stripe
documentation](https://docs.stripe.com/billing/subscriptions/usage-based/alerts),
usage alerts are **one-time per customer** - they trigger once and only
consider usage reported after the alert is created. This means we need
to create a new alert whenever:

1.  Subscription is created (trial) - Already implemented
2.  Trial ends (user becomes Active subscriber) - **Added in this PR**
3.  Credit tier changes (upgrade) - **Added in this PR**
4.  **New billing cycle starts** - **Critical fix in this PR!**

## The Issue

Previously, the invoice webhook reset `hasReachedCurrentPeriodCap =
false` at cycle end, but didn't create a new alert. This meant after the
first billing period, there was no alert to trigger and users could
exceed their limit without being blocked.

## Changes

### 1. Invoice Webhook (`billing-webhook-invoice.service.ts`)
When invoice is finalized for `subscription_cycle`:
- Reset `hasReachedCurrentPeriodCap = false`  (already done)
- **NEW**: Create alert at the current tier cap

### 2. End Trial Period (`billing-subscription.service.ts`)
In `endTrialPeriod()`:
- **NEW**: Create alert at the paid tier cap (not trial cap)

### 3. Credit Tier Upgrades (`billing-subscription.service.ts`)
In `changeMeteredPrice()`, when upgrading immediately (not scheduled for
period end):
- **NEW**: Reset `hasReachedCurrentPeriodCap = false`
- **NEW**: Create alert at new tier cap

### 4. Alert Title Update (`stripe-billing-alert.service.ts`)
Changed from "Trial usage cap" to "Usage cap" since alerts are now used
for all scenarios.

## Stripe Alert Limits

- Max 25 alerts per meter+customer combination
- Alerts only evaluate usage reported after creation
- One-time alerts trigger once per customer

With monthly billing cycles + occasional tier changes, we should stay
well under the 25 alert limit.

## Testing

- TypeScript typechecking passes
- Backend services properly inject new dependencies
2025-12-24 16:32:31 +01:00
github-actions[bot] 5d6c39ec7e i18n - translations (#16800)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-24 15:47:58 +01:00
Félix Malfait b46e9d2e64 feat: add AI chat error handling for billing and API key errors (#16797)
## Summary

This PR adds user-friendly error handling for AI chat features,
specifically for **billing credits exhausted** and **API key not
configured** errors.

## Changes

### Backend
- Added `BILLING_CREDITS_EXHAUSTED` exception code with 402 status
- Added `API_KEY_NOT_CONFIGURED` exception code with 503 status
- Added billing check before AI chat streaming in
`agent-chat.controller.ts`
- Added error code to HTTP exception response body for frontend error
type detection
- Created `AgentRestApiExceptionFilter` for agent-specific errors

### Frontend
- Created `AIChatBanner` - reusable banner component for error/warning
messages
- Created `AIChatCreditsExhaustedMessage` - shows upgrade prompts based
on user permissions
- Created `AIChatApiKeyNotConfiguredMessage` - shows configuration
guidance with docs link
- Created `AIChatErrorRenderer` - encapsulates error type switching
logic (fixes nested ternary)
- Created `AIChatStandaloneError` - displays errors when there are no
messages
- Split `aiChatErrorUtils.ts` into separate files (1 export per file):
  - `AIChatErrorCode.ts`
  - `extractErrorCode.ts`
  - `isAIChatErrorOfType.ts`
  - `isBillingCreditsExhaustedError.ts`
  - `isApiKeyNotConfiguredError.ts`
- Added comprehensive test coverage (27 tests)

### Other
- Updated trial period banner messaging

## Testing
- All lint checks pass
- All 27 new tests pass
- TypeScript typecheck passes
2025-12-24 15:30:28 +01:00
Etienne bc0ffc98bb Billing - fixes and updates (#16796)
Improvements :
- phase 2 date calculation issue
- quantity update issue
- unnecessary phase creation - schedule should be created only when a
next phase is planned
2025-12-24 14:18:06 +00:00
github-actions[bot] 17e923b908 i18n - translations (#16788)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-23 19:01:31 +01:00
Lucas Bordeau 0b5be7caa3 Refactored Date to Temporal in critical date zones (#16544)
Fixes https://github.com/twentyhq/twenty/issues/16110

This PR implements Temporal to replace the legacy Date object, in all
features that are time zone sensitive. (around 80% of the app)

Here we define a few utils to handle Temporal primitives and obtain an
easier DX for timezone manipulation, front end and back end.

This PR deactivates the usage of timezone from the graph configuration,
because for now it's always UTC and is not really relevant, let's handle
that later.

Workflows code and backend only code that don't take user input are
using UTC time zone, the affected utils have not been refactored yet
because this PR is big enough.

# New way of filtering on date intervals

As we'll progressively rollup Temporal everywhere in the codebase and
remove `Date` JS object everywhere possible, we'll use the way to filter
that is recommended by Temporal.

This way of filtering on date intervals involves half-open intervals,
and is the preferred way to avoid edge-cases with DST and smallest time
increment edge-case.

## Filtering endOfX with DST edge-cases

Some day-light save time shifts involve having no existing hour, or even
day on certain days, for example Samoa Islands have no 30th of December
2011 : https://www.timeanddate.com/news/time/samoa-dateline.html, it
jumps from 29th to 31st, so filtering on `< next period start` makes it
easier to let the date library handle the strict inferior comparison,
than filtering on `≤ end of period` and trying to compute manually the
end of the period.

For example for Samoa Islands, is end of day `2011-12-29T23:59:59.999`
or is it `2011-12-30T23:59:59.999` ? If you say I don't need to know and
compute it, because I want everything strictly before
`2011-12-29T00:00:00 + start of next day (according to the library which
knows those edge-cases)`, then you have a 100% deterministic way of
computing date intervals in any timezone, for any day of any year.

Of course the Samoa example is an extreme one, but more common ones
involve DST shifts of 1 hour, which are still problematic on certain
days of the year.

## Computing the exact _end of period_

Having an open interval filtering, with `[included - included]` instead
of half-open `[included - excluded)`, forces to compute the open end of
an interval, which often involves taking an arbitrary unit like minute,
second, microsecond or nanosecond, which will lead to edge-case of
unhandled values.

For example, let's say my code computes endOfDay by setting the time to
`23:59:59.999`, if another library, API, or anything else, ends up
giving me a date-time with another time precision `23:59:59.999999999`
(down to the nanosecond), then this date-time will be filtered out,
while it should not.

The good deterministic way to avoid 100% of those complex bugs is to
create a half-open filter :

`≥ start of period` to `< start of next period` 

For example : 

`≥ 2025-01-01T00:00:00` to `< 2025-01-02T00:00:00` instead of `≥
2025-01-01T00:00:00` to `≤ 2025-01-01T23:59:59.999`

Because, `2025-01-01T00:00:00` = `2025-01-01T00:00:00.000` =
`2025-01-01T00:00:00.000000` = `2025-01-01T00:00:00.000000000` => no
risk of error in computing start of period

But `2025-01-01T23:59:59` ≠ `2025-01-01T23:59:59.999` ≠
`2025-01-01T23:59:59.999999` ≠ `2025-01-01T23:59:59.999999999` =>
existing risk of error in computing end of period

This is why an half-open interval has no risk of error in computing a
date-time interval filter.

Here is a link to this debate :
https://github.com/tc39/proposal-temporal/issues/2568

> For this reason, we recommend not calculating the exact nanosecond at
the end of the day if it's not absolutely necessary. For example, if
it's needed for <= comparisons, we recommend just changing the
comparison code. So instead of <= zdtEndOfDay your code could be <
zdtStartOfNextDay which is easier to calculate and not subject to the
issue of not knowing which unit is the right one.
>
> [Justin Grant](https://github.com/justingrant), top contributor of
Temporal

## Application to our codebase

Applying this half-open filtering paradigm to our codebase means we
would have to rename `IS_AFTER` to `IS_AFTER_OR_EQUAL` and to keep
`IS_BEFORE` (or even `IS_STRICTLY_BEFORE`) to make this half-open
interval self-explanatory everywhere in the codebase, this will avoid
any confusion.

See the relevant issue :
https://github.com/twentyhq/core-team-issues/issues/2010

In the mean time, we'll keep this operand and add this semantic in the
naming everywhere possible.

## Example with a different user timezone 

Example on a graph grouped by week in timezone Pacific/Samoa, on a
computer running on Europe/Paris :

<img width="342" height="511" alt="image"
src="https://github.com/user-attachments/assets/9e7d5121-ecc4-4233-835b-f59293fbd8c8"
/>

Then the associated data in the table view, with our **half-open
date-time filter** :

<img width="804" height="262" alt="image"
src="https://github.com/user-attachments/assets/28efe1d7-d2fc-4aec-b521-bada7f980447"
/>

And the associated SQL query result to see how DATE_TRUNC in Postgres
applies its internal start of week logic :

<img width="709" height="220" alt="image"
src="https://github.com/user-attachments/assets/4d0542e1-eaae-4b4b-afa9-5005f48ffdca"
/>

The associated SQL query without parameters to test in your SQL client :

```SQL
SELECT "opportunity"."closeDate" as "close_date", TO_CHAR(DATE_TRUNC('week', "opportunity"."closeDate", 'Pacific/Samoa') AT TIME ZONE 'Pacific/Samoa', 'YYYY-MM-DD') AS "DATE_TRUNC by week start in timezone Pacific/Samoa", "opportunity"."name" FROM "workspace_1wgvd1injqtife6y4rvfbu3h5"."opportunity" "opportunity" ORDER BY "opportunity"."closeDate" ASC NULLS LAST
```

# Date picker simplification (not in this PR)

Our DatePicker component, which is wrapping `react-datepicker` library
component, is now exposing plain dates as string instead of Date object.
The Date object is still used internally to manage the library
component, but since the date picker calendar is only manipulating plain
dates, there is no need to add timezone management to it, and no need to
expose a handleChange with Date object.

The timezone management relies on date time inputs now.

The modification has been made in a previous PR :
https://github.com/twentyhq/twenty/issues/15377 but it's good to
reference it here.

# Calendar feature refactor

Calendar feature has been refactored to rely on Temporal.PlainDate as
much as possible, while leaving some date-fns utils to avoid re-coding
them.

Since the trick is to use utils to convert back and from Date object in
exec env reliably, we can do it everywhere we need to interface legacy
Date object utils and Temporal related code.

## TimeZone is now shown on Calendar :

<img width="894" height="958" alt="image"
src="https://github.com/user-attachments/assets/231f8107-fad6-4786-b532-456692c20f1d"
/>

## Month picker has been refactored 

<img width="503" height="266" alt="image"
src="https://github.com/user-attachments/assets/cb90bc34-6c4d-436d-93bc-4b6fb00de7f5"
/>

Since the days weren't useful, the picker has been refactored to remove
the days.

# Miscellaneous 
- Fixed a bug with drag and drop edge-case with 2 items in a list.

# Improvements 

## Lots of chained operations
It would be nice to create small utils to avoid repeated chained
operations, but that is how Temporal is designed, a very small set of
primitive operations that allow to compose everything needed. Maybe
we'll have wrappers on top of Temporal in the coming years.

## Creation of Temporal objects is throwing errors

If the input is badly formatted Temporal will throw, we might want to
adopt a global strategy to avoid that.

Example : 

```ts
const newPlainDate = Temporal.PlainDate.from('bad-string'); // Will throw
```
2025-12-23 17:40:26 +00:00
github-actions[bot] 67f511bae3 i18n - translations (#16776)
Created by Github action

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Broad i18n refresh touching email and front-end locales, with
new/updated strings and some path/key adjustments.
> 
> - Adds "Updates" settings page strings (e.g., `Updates`, `Early
access`, `Read changelog`, "Check out our latest releases") and
removes/replaces prior "Releases/Changelog" entries
> - Introduces chart color palette label (`Default palette`) and page
layout field widget messages (`No field configured`, `Select a field to
display…`, `No related records`)
> - Updates locale files for many languages (af-ZA, ar-SA, ca-ES, cs-CZ,
da-DK, de-DE, el-GR, es-ES, fi-FI, aa-ER) with new or corrected
translations and component path changes
> - Email (vi-VN): adjusts generated translations; leaves some strings
untranslated and clears one obsolete translation in `vi-VN.po`
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0246b729afcb58fe43891fbcd99fd6ab825291c9. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-12-23 14:32:25 +01:00
Félix Malfait c2c8f6e41c fix: prevent API keys from creating UNLISTED views (#16770)
## Summary
UNLISTED views are personal views tied to a specific user, so API keys
should not be able to create them.

## Changes
- Added check in `canUserCreateView` to block API keys from creating
UNLISTED views
- Refactored the service to use smaller functions with early returns (no
nested if/else)

## Behavior Matrix

### Creating Views
| Caller | Visibility | Has VIEWS Permission | Result |
|--------|------------|---------------------|--------|
| User | UNLISTED | (not checked) |  Allow |
| User | WORKSPACE | Yes |  Allow |
| User | WORKSPACE | No |  Denied |
| **API Key** | **UNLISTED** | (not checked) | ** Denied** |
| API Key | WORKSPACE | Yes |  Allow |
| API Key | WORKSPACE | No |  Denied |
2025-12-23 10:13:00 +01:00
eeeggg 65dced14ff fix: enable API key management of workspace views + fix permission bypass vulnerability (#16768)
Fixes #16739

- Remove empty string coercion in createCoreView that caused PostgreSQL
UUID errors for API keys
- Add permission check allowing API keys with VIEWS permission to manage
workspace views they created

API keys with 'Manage Views' permission can now create, update, and
delete workspace views via both GraphQL and REST APIs.
2025-12-23 09:06:47 +01:00