Commit Graph
458 Commits
Author SHA1 Message Date
WeikoandGitHub 083a038f8a Deprecate workspace datasoure (#16507) 2025-12-11 18:27:49 +01:00
Charles BochetandGitHub a18203934c Fix flat entity maps date serialization (#16420)
Changes:
- as we store date in redis as serialized, let's make all flatEntity
dates as string. This requires changing FlatEntity types and making sure
that entity are converted to flatEntity and flatEntity to dtos
2025-12-09 16:41:50 +01:00
martmullandGitHub 7f1e69740a 1895 extensibility v1 application tokens (#16365)
First PR to implement application tokens
- add new application role in twenty-server
- move duplicated constants and types to twenty-shared
- will  add role configuration utils into twenty-sdk in another PR
2025-12-08 10:42:46 +01:00
EtienneandGitHub 1981cf0ed6 Query complexity - Fix rest metadata api limit (#16336) 2025-12-04 17:24:25 +01:00
EtienneandGitHub d1befa7e35 Query complexity validation (#16274)
Validations : 
- relations count (in common api)
- oneToMany relation nested count (in common)
- requested fields count (in gql)
- root resolver count (in gql)
- root resolver duplicates (in gql)
- specific complexity for metadata / nesting count (in gql)
2025-12-04 16:33:08 +01:00
EtienneandGitHub c68eb35043 Remove suggestion in gql error for un-authenticated users (#16328)
Closes https://github.com/twentyhq/private-issues/issues/357
Closes https://github.com/twentyhq/private-issues/issues/358
2025-12-04 16:23:49 +01:00
8716cb25e9 Add message channel reset command (#16266)
In this PR, we are adding a new command to reset a message channel.

We are also refactoring a bit cursor reset as we had multiple
implementations at different places in the code base

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-12-04 14:25:41 +01:00
WeikoandGitHub 13e283fc3a Rename roleTargets -> roleTarget (#16247) 2025-12-02 14:39:54 +01:00
Lucas BordeauandGitHub 2691222d5f Improve board experience 🖼️ (#16063)
This PR improves the general UX and DX of boards, by modifying the query
effect to only use paged group by queries.

In this PR we implement two more things in the backend for group by
queries :
- Fixed ORDER BY in the PARTITION BY sub-query (this wasn't working
because it was applied in the main query, so it sorted randomly picked
records, which was a correct sort on an incorrect dataset returned by
the sub-query)
- Added offset paging in PARTITION BY

Miscellaneous, various bug fixes and improvements along the way : 
- Throttled loading of cards to avoid React freeze
- Handling of drag & drop
- Handling of create / delete / update
- Reworked skeleton (the library slows down a lot with hundreds of
skeleton for a spinning effect that is hardly noticed)
- Fixed refetch of aggregate queries (I included the new group by
aggregates query we use in the existing refetch mechanism)
- Re-trigger queries on filters and sorts changes
- Unselect all record ids when deleting / restoring / detroying
- Fetch only groups that still have records to lighten the group by
query.

# What remains to be done 

This is still a naïve fetch more implementation that will work for a few
fetch more rounds, but if you scroll and load say 200 cards per column
on a board, React will re-render all 200 cards of each column each time.

We would probably need to virtualize the board with paged queries as we
did for the table, this could be done after this PR but seems less
urgent.

What's nice is that this new query pattern is well designed for
virtualization also, drawing from our experience with table
virtualization, and adapted to a multi-column request pattern, like a
2:2 matrix of records, for our boards.

So the remaining work would be to design a UI solution for virtualizing
this matrix of records, which could be quite different from our table
virtualization mechanism.
2025-12-02 10:09:29 +00:00
Paul RastoinandGitHub 9f62188ba6 Field and object metadata naming does not refer to v2 (#16187)
Related https://github.com/twentyhq/core-team-issues/issues/1911
2025-11-28 18:06:44 +01:00
MarieandGitHub fd9ea2f5ee [groupBy] Fix order by nested date field (#16135)
Fixes https://github.com/twentyhq/core-team-issues/issues/1935
2025-11-28 09:02:06 +00:00
WeikoandGitHub da626f70b7 optimize buildFieldMapsFromFlatObjectMetadata usages (#16132)
This newly introduced util can be a bit expensive especially when done
recursively.
This PR improves that


File | Pattern Fixed | Impact
-- | -- | --
format-result.util.ts | Recursive array/object processing | N array
items → 1 call
format-data.util.ts | Recursive array processing | N array items → 1
call
process-nested-relations-v2.helper.ts | Duplicate call in call chain | 2
calls → 1 call
common-result-getters.service.ts | Per-record processing in array | N
records → 1 call
compute-relation-connect-query-configs.util.ts | Nested loop (entities ×
connect fields) | N×M calls → 1 call
2025-11-27 15:01:51 +01:00
WeikoandGitHub 1607aebcc6 Deprecate object metadata maps in favor of flat entities (#16080)
## Context
Deprecating the old objectMetadataMap type in favour of split flat
entities to match with our new caching.
In the long run, trying to achieve:
- Better performance through caching
- Consistent data access patterns across the codebase
- Reduced database queries

Now that everything is based on flat entities, which are cached, we can
finish the refactoring of workspace context cache which should already
improve performances.
Then the last step will be to consume that new cache in the new global
datasource to get rid of the many workspace datasources stored in the
server
2025-11-27 13:43:34 +01:00
MarieandGitHub 9c9a01d55a [groupBy][Requires cache flush] Add WEEK date granularity (#16099)
Closes https://github.com/twentyhq/core-team-issues/issues/1921

https://github.com/user-attachments/assets/bf400ec1-ce25-4d9e-b875-774168452514
2025-11-27 10:22:57 +00:00
EtienneandGitHub 70f48ba445 Security - Add complexity max on gql queries (#16069)
closes https://github.com/twentyhq/private-issues/issues/348
closes https://github.com/twentyhq/private-issues/issues/352
closes https://github.com/twentyhq/private-issues/issues/353
closes https://github.com/twentyhq/private-issues/issues/354
2025-11-26 18:36:21 +01:00
EtienneandGitHub 71724de7dd Security - disable gql introspection for non-auth user (#16047)
closes https://github.com/twentyhq/private-issues/issues/351
closes https://github.com/twentyhq/private-issues/issues/350

Before, introspection query works without token. After, fails.

```

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    subscriptionType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  description
  type {
    ...TypeRef
  }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}
```
2025-11-25 15:39:29 +01:00
MarieandGitHub 46d1ea6505 [groupBy] groupBy relation fields (#15951)
Example query
Here person has 
- a N - 1 relationship with company
- a N - 1 morph relationship with pet or company

<img width="862" height="374" alt="image"
src="https://github.com/user-attachments/assets/59bc9b82-c943-43de-ad82-d3393b76904b"
/>
<img width="415" height="629" alt="image"
src="https://github.com/user-attachments/assets/9a3176bc-99cd-4983-8611-68ca3a2cf527"
/>

truncated response
<img width="299" height="447" alt="image"
src="https://github.com/user-attachments/assets/45af0322-9e66-4eae-8353-6c0dda487bbe"
/>

We don't allow grouping by relations of relations.

Left to do
- rest api
- tests on permissions
2025-11-25 09:03:16 +00:00
WeikoandGitHub 04562b11fb Migrate metadata cache (#16030)
## Context
Deprecating legacy ObjectMetadata from cache in favor of flat entities.
Introducing utils to build byName/byNameSingular/byNamePlural in
isolated cases

## Next
- I had to introduce a util to build from flat to legacy
objectMetadataMaps, we should instead use flat maps directly when needed
(datasource, schema generation, etc)
- Deprecate metadata version in the cache
- Use the new cache strategy for flat entities with permissions and
feature flags and inject in the global datasource context
2025-11-24 19:50:47 +01:00
EtienneandGitHub 208c0857ee common api - null equivalence (#15926)
closes https://github.com/twentyhq/core-team-issues/issues/1629

To do before requesting review : 
- filter update

Migration to come in an other PR


Strat : 
1/  Null transformation

- [x] Transform NULL equivalent value to NULL in field validation in
common api - pre-query - with feature flag
- [ ] Same logic in ORM (Not done, complex to handle feature flag here)
- [x] Transform NULL value to equivalent in data formatting in ORM -
post-query

2/ Migration (in other PR) for fieldMetadata not nullable with default
defaultValue (empty string, ...)

- [ ] Remove NOT NULL db constraint
- [ ] Update record value to NULL
- [ ] Update field metadata : isNullable:true
- [ ] Update uniqueIndex whereClause (also for standard uniqueIndex) 
- [ ] Activate feature flag

3/ Update metadata creation

- [x] No more default default value
- [x] Update standard field nullability
- [x] Remove index default whereClause for standard field

4/ Update filter

- [x] When filtering on NULL or empty string, be sure all records are
returned (the one with NULL + the one with "")

5/ Test

- [ ] Strat. to do
2025-11-24 18:57:47 +01:00
fdb4e4ef10 [DASHBOARDS] Use a time scale when the primary axis is a date on the Bar Chart (#15932)
Closes https://github.com/twentyhq/core-team-issues/issues/1891

Create empty buckets according to the date granularity

Video QA:


https://github.com/user-attachments/assets/86c0f817-35b3-4bab-b093-d11491684b82

Note: We would also need to create empty buckets for cyclic
granularities (DAY_OF_THE_WEEK, MONTH_OF_THE_YEAR, QUARTER_OF_THE_YEAR).
TODO:
- Always order the cyclic granularities Monday -> Sunday (take
firstDayOfTheWeek into account), January -> December, Q1 -> Q4. For now
they are returned by the backend in alphabetical order, which doesn't
make much sense
- Remove the translation into the user's locale of these granularities
from the backend because otherwise we can't reconstruct the missing days
or month in the frontend since they will be translated

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
2025-11-21 10:24:19 +01:00
MarieandGitHub 5bb4abc23d Add optional limit variable to groupBy queries (#15885)
Closes https://github.com/twentyhq/core-team-issues/issues/1600.

Two remarks 
- This `limit` variable does not reduce postgre's work at it still needs
to scan the whole table. It did not seem possible to me to optimize this
as we cannot foresee which dimensions will be used by the user, and an
optimization could only result from an index on the dimension(s) (e.g.:
group companies by addressCity limit 50 can be optimized if we have an
index on companies.addressCity + we had a default orderBy on
adressCity). But this will still optimize the FE which at the moment
receives all groups and truncates the result.
- I have not done the work on the FE as the addition of limit is a
breaking change, and will break until the workspaces' schema is rebuilt,
so we need to flush the cache. I think this could be acceptable as the
feature is in the lab but I preferred not doing it yet as it would have
no impact since in the BE I added a default limit to 50 groups, and I
expect more FE work will be done to allow the user to choose their own
limit
2025-11-18 17:25:33 +01:00
76cc07a5f7 Revert x2 - Common - Field validation (#15491) (#15879)
Revert https://github.com/twentyhq/twenty/pull/15821

Should not have been reverted. Conflicting with this
[PR](https://github.com/twentyhq/twenty/pull/15742),
[reverted](https://github.com/twentyhq/twenty/pull/15875)

Co-authored-by: Weiko <corentin@twenty.com>
2025-11-18 12:04:00 +01:00
martmullandGitHub d0d90735cb Revert "refactor: Simplify CRUD services to leverage Common API (#157… (#15875)
This reverts commit 11e07f90d2.
2025-11-18 09:44:15 +01:00
Paul RastoinandGitHub a39efeb1ab [BREAKING_CHANGE/GRAPHQL/OBJECT_METADATA_CREATE_ONE] Remove object/fields/view-fields v1 implementation (#15823)
# Introduction
Remove the v2 feature flag for view-field field-metadata and
object-metadata metadata entities

## Some details
- Disabled nestjs-query for object metadata creation and explicitly
calling it
- removed all v1 integration tests files

## Remarks
Not remove v2 referencing in both filenaming right now will handle that
globally later

## Breaking change
Due to object metadata resolver createOne standardization had to rename
the input from `CreateObjectInput` to `CreateOneObjectInput`
2025-11-17 10:55:19 +01:00
Félix MalfaitandGitHub 5dfb66917c Upgrade NestJS from 10.x to 11.x (#15836)
## Overview
This PR upgrades all NestJS dependencies from version 10.x to 11.x,
following the [official migration
guide](https://docs.nestjs.com/migration-guide). This builds on top of
the v9 to v10 upgrade completed in PR #15835.

## Changes

### Dependencies Updated
**Core packages (10.x → 11.x):**
- `@nestjs/common`: 10.4.16 → 11.0.8
- `@nestjs/core`: 10.4.16 → 11.0.8
- `@nestjs/platform-express`: 10.4.16 → 11.0.8
- `@nestjs/config`: 3.2.3 → 3.3.0
- `@nestjs/passport`: 10.0.3 → 11.0.0
- `@nestjs/axios`: 3.0.2 → 3.1.2
- `@nestjs/schedule`: ^3.0.0 → ^4.1.1
- `@nestjs/serve-static`: 4.0.2 → 5.0.1
- `@nestjs/cache-manager`: ^2.2.1 → ^2.3.0
- `@nestjs/jwt`: 10.2.0 → 11.0.0
- `@nestjs/typeorm`: 10.0.2 → 11.0.0
- `@nestjs/terminus`: 11.0.0 (already on v11)
- `@nestjs/event-emitter`: 2.1.0 (compatible)

**DevDependencies:**
- `@nestjs/testing`: ^10.4.16 → ^11.0.8
- `@nestjs/schematics`: ^10.1.0 → ^11.0.2
- `@nestjs/cli`: 10.3.0 → 11.0.0

### Code Changes
**Fixed: TwentyConfigModule conditional imports**
- Updated `TwentyConfigModule.forRoot()` to use spread operator for
conditional imports
- Fixes TypeScript error with NestJS 11's stricter DynamicModule type
checking

**Cleanup: Removed unused package**
- Removed `@revertdotdev/revert-react` (not being used anywhere in the
codebase)

## Breaking Changes Addressed

### 1.  Reflector Type Inference
- **Impact**: None - codebase only uses `reflector.get()` method
- **Analysis**: Does not use `getAllAndMerge()` or `getAllAndOverride()`
(the methods with breaking changes)
- **Files reviewed**: feature-flag.guard.ts,
message-queue-metadata.accessor.ts,
workspace-query-hook-metadata.accessor.ts

### 2.  Lifecycle Hooks Execution Order
- **Change**: Termination hooks (`OnModuleDestroy`,
`BeforeApplicationShutdown`, `OnApplicationShutdown`) now execute in
REVERSE order
- **Analysis**: Reviewed all lifecycle hook implementations
  - Redis client cleanup
  - Database connection cleanup (GlobalWorkspaceDataSource)
  - BullMQ queue/worker cleanup
  - Cache storage cleanup
- **Result**: Dependency order is safe - services using connections
clean up before the connections themselves

### 3.  Middleware Registration Order
- **Change**: Global middleware now executes first regardless of import
order
- **Analysis**: Middleware is not registered as global, so execution
order remains consistent
- **Files reviewed**: app.module.ts, middleware.module.ts

## Testing

All tests passing and build successful:

**Unit Tests (283+ tests):**
-  Health module: 38 tests passed
-  Auth module: 115 tests passed (passport v11 integration)
-  REST API: 90 tests passed (middleware and express platform)
-  Feature flags: 17 tests passed (Reflector usage)
-  Workspace: 23 tests passed

**Build & Quality:**
-  Type checking: Passed
-  Linting: Passed
-  Build: 3,683 files compiled successfully

## Verification

Tested critical NestJS functionality:
-  Authentication & Security (JWT, OAuth, guards)
-  HTTP Platform (Express integration, REST endpoints)
-  Dependency Injection (Services, factories, providers)
-  Cache Management (Redis with @nestjs/cache-manager)
-  GraphQL (Query runners, resolvers)
-  Configuration (Environment config)
-  Scheduling (Cron jobs with @nestjs/schedule v4)
-  Lifecycle Hooks (Module initialization and cleanup)
-  Reflector (Metadata reflection in guards)

## Related PRs
- #15835 - Upgrade NestJS from 9.x to 10.x (completed)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Upgrades NestJS to v11 and updates routing patterns, auth strategies,
GraphQL schema options, and build/dist paths (scripts, Docker, Nx,
migrations, assets), plus enables Devtools in development.
> 
> - **Backend (NestJS 11 upgrade)**:
> - Bump `@nestjs/*` packages (core, platform-express, jwt, passport,
typeorm, serve-static, schedule, cli/testing/schematics) to v11.
> - Update REST/route-trigger/file controllers to new wildcard syntax
(`*path`).
> - Refactor OAuth (Google/Microsoft) and SAML strategies (abstract base
+ explicit `validate`); minor typings.
>   - Enable `DevtoolsModule` in development.
> - **GraphQL**:
> - Add `buildSchemaOptions.orphanedTypes` for client-config types; keep
Yoga/Sentry setup.
> - **Build/Runtime & Config**:
> - Standardize dist layout (remove `src` in paths): update scripts,
Docker `CMD`, Nx `project.json`, render scripts, TypeORM migration
paths, asset resolution.
> - Adjust `nest-cli.json` (watchOptions, asset globs, migrations
outDir, monorepo/root).
> - Improve config module imports (spread conditional); tsconfig
excludes `node_modules`.
>   - Minor Nx default: `start` target caching disabled.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1139fd85a9. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-11-16 18:20:06 +01:00
martmullandGitHub 867422627c Revert "Common - Field validation (#15491)" (#15821)
This reverts commit 261cba40ef.
2025-11-14 13:47:22 +01:00
261cba40ef Common - Field validation (#15491)
Closes : https://github.com/twentyhq/core-team-issues/issues/1622

To do in other PR : 
- Add migration command for non nullable text, raw_json & array fields
- Add null transformation

---------

Co-authored-by: Weiko <corentin@twenty.com>
2025-11-13 15:45:35 +01:00
Félix MalfaitandGitHub 11e07f90d2 refactor: Simplify CRUD services to leverage Common API (#15742)
## Summary

This PR refactors all Record CRUD services to use the Common API
(CommonQueryRunners) instead of directly accessing TwentyORM, achieving
**77% code reduction** while maintaining full functionality.

## Changes

### Code Reduction: -547 net lines (77%)
- **Before**: 901 lines across 5 services
- **After**: 354 lines (services + utility)
- **Deleted**: 547 lines of redundant code

### Services Simplified

| Service | Before | After | Reduction |
|---------|--------|-------|-----------|
| CreateRecordService | 142 | 59 | -83 lines |
| UpdateRecordService | 177 | 51 | -126 lines |
| DeleteRecordService | 137 | 51 | -86 lines |
| FindRecordsService | 233 | 68 | -165 lines |
| UpsertRecordService | 212 | 129 | -83 lines |

### Architecture Change

**Before:**
```
CRUD Services → TwentyORM
- Manual query building
- Manual permission checking
- Manual transformations
- Duplicated logic
```

**After:**
```
CRUD Services → CommonQueryRunners → TwentyORM
- Common API handles queries
- Common API handles permissions
- Common API handles transformations
- Single source of truth
```

### Module Dependencies Simplified

**Removed:**
- TwentyORMModule
- RecordPositionModule
- RecordTransformerModule
- WorkflowCommonModule

**Added:**
- CoreCommonApiModule
- WorkspaceMetadataCacheModule

**From 4 heavy dependencies → 2 clean dependencies**

## What Changed

### New Code
- `common-api-context-builder.util.ts` - Single shared utility (68
lines)

### Refactored Services
All services now follow a simple pattern:
1. Build Common API context
2. Call appropriate CommonQueryRunner
3. Return formatted result

Each service is now 50-120 lines instead of 140-230 lines.

### Type Fixes
- Fixed `FindRecordsParams.orderBy` type (was
`Partial<ObjectRecordOrderBy>`, now `ObjectRecordOrderBy`)
- Fixed `FindRecordsInput.gqlOperationOrderBy` type (same fix)

## Benefits

###  Code Quality
- 77% less code to maintain
- No code duplication
- Simpler, clearer logic
- Proper TypeScript types (no hacks)

###  Common API Integration
All services now get Common API benefits:
- Consistent permission checking
- Query hooks (before/after execution)
- Automatic input transformation
- Automatic position handling
- Result processing and enrichment
- Same behavior as REST/GraphQL

###  Safety Preserved
- createdBy actor metadata preserved for workflows
- All field validation maintained
- All transformations maintained
- All error handling maintained

###  No Breaking Changes
- Same external API for all services
- Workflows continue to work
- AI operations continue to work
- MCP operations continue to work

## Testing

-  TypeScript compiles
-  All files pass linting
-  No type casting hacks
-  Proper type safety throughout
- ⚠️ Integration tests recommended

## What Common API Handles For Us

1. **Record Position** - Automatic via `RecordPositionService`
2. **Input Transformation** - Automatic for NUMBER, RICH_TEXT, PHONES,
EMAILS, LINKS
3. **createdBy Actor** - Injected via `CreatedByCreateOnePreQueryHook` +
explicit workflow actor
4. **Field Validation** - Only processes valid fields
5. **Permissions** - Validates permissions before execution
6. **Query Hooks** - Before/after execution hooks work
7. **Error Handling** - Consistent exception handling

## Files Modified (12)

**Services (6):**
- create-record.service.ts
- update-record.service.ts
- delete-record.service.ts
- find-records.service.ts
- upsert-record.service.ts
- record-crud.module.ts

**Types (2):**
- find-records-params.type.ts
- record-crud-input.type.ts

**Workflows (1):**
- find-records.workflow-action.ts

**New Files (3):**
- common-api-context-builder.util.ts
- REFACTORING_COMPLETE.md
- REFACTORING_ANALYSIS.md

## Verification Checklist

- [x] TypeScript compiles
- [x] Linter passes
- [x] No type hacks (`as unknown as` removed)
- [x] createdBy preserved for workflows
- [x] Module dependencies simplified
- [x] Common API integration complete
- [ ] Integration tests pass (recommended)
- [ ] Workflow execution tested (recommended)

## Related Issues

This refactoring establishes the pattern for making CRUD operations
consistent across all presentation layers (REST, GraphQL,
Tools/Workflows/AI/MCP).
2025-11-11 11:07:36 +01:00
martmullandGitHub 9880f192a5 Move composite types to twenty-shared (#15741) 2025-11-10 22:11:52 +01:00
Félix MalfaitandGitHub cff17db6cb Enhance role-check system with stricter checks (#15392)
## Overview

This PR strengthens our permission system by introducing more granular
role-based access control across the platform.

## Changes

### New Permissions Added
- **Applications** - Control who can install and manage applications
- **Layouts** - Control who can customize page layouts and UI structure
- **AI** - Control access to AI features and agents
- **Upload File** - Separate permission for file uploads
- **Download File** - Separate permission for file downloads (frontend
visibility)

### Security Enhancements
- Implemented whitelist-based validation for workspace field updates
- Added explicit permission guards to core entity resolvers
- Enhanced ESLint rule to enforce permission checks on all mutations
- Created `CustomPermissionGuard` and `NoPermissionGuard` for better
code documentation

### Affected Components
- Core entity resolvers: webhooks, files, domains, applications,
layouts, postgres credentials
- Workspace update mutations now use whitelist validation
- Settings UI updated with new permission controls

### Developer Experience
- ESLint now catches missing permission guards during development
- Explicit guard markers make permission requirements clear in code
review
- Comprehensive test coverage for new permission logic

## Testing
-  All TypeScript type checks pass
-  ESLint validation passes
-  New permission guards properly enforced
-  Frontend UI displays new permissions correctly

## Migration Notes
Existing workspaces will need to assign the new permissions to roles as
needed. By default, all new permissions are set to `false` for non-admin
roles.
2025-11-07 15:37:17 +01:00
GuillimandGitHub 2274a937bd first step (#15687)
TODO : 

-> for test integreation for this connect 
-> add validation in common API

Fixes https://github.com/twentyhq/core-team-issues/issues/1278
2025-11-07 13:49:57 +00:00
martmullandGitHub abde3c04ac 1630 extensibility twenty cli ability to create edit and delete fields (#15501)
As title

- adds decorators in twenty-sdk
- update twenty-cli load-manifest to it gets @FieldMetadata infos +
testing
- update twenty-server so it CRUD fields properly, using
universalIdentifier
- Fix UI so we can update managed objects records
- move FieldMetadata items from twenty-server to twenty-shared
2025-11-05 17:50:06 +01:00
9e1c854b27 fix: updated dryrun to return correct populated record (#15484)
## Description

- This PR approaches to solve
https://github.com/twentyhq/twenty/issues/15201
- updated `createDryRunResponse` to return fully populated merged record
- this way the frontend can render the populated data it as-is without
recomputing relations
- Dry-run now uses the same nested-relations population path as the
non-dryRun flow

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2025-11-03 13:50:14 +01:00
afeb505eed [Breaking Change] Implement reliable date picker utils to handle all timezone combinations (#15377)
This PR implements the necessary tools to have `react-datepicker`
calendar and our date picker components work reliably no matter the
timezone difference between the user execution environment and the user
application timezone.

Fixes https://github.com/twentyhq/core-team-issues/issues/1781

This PR won't cover everything needed to have Twenty handle timezone
properly, here is the follow-up issue :
https://github.com/twentyhq/core-team-issues/issues/1807

# Features in this PR

This PR brings a lot of features that have to be merged together.

- DATE field type is now handled as string only, because it shouldn't
involve timezone nor the JS Date object at all, since it is a day like a
birthday date, and not an absolute point in time.
- DATE_TIME field wasn't properly handled when the user settings
timezone was different from the system one
- A timezone abbreviation suffix has been added to most DATE_TIME
display component, only when the timezone is different from the system
one in the settings.
- A lot of bugs, small features and improvements have been made here :
https://github.com/twentyhq/core-team-issues/issues/1781

# Handling of timezones

## Essential concepts

This topic is so complex and easy to misunderstand that it is necessary
to define the precise terms and concepts first. It resembles character
encoding and should be treated with the same care.

- Wall-clock time : the time expressed in the timezone of a user, it is
distinct from the absolute point in time it points to, much like a
pointer being a different value than the value that it points to.
- Absolute time : a point in time, regardless of the timezone, it is an
objective point in time, of course it has to be expressed in a given
timezone, because we have to talk about when it is located in time
between humans, but it is in fact distinct from any wall clock time, it
exists in itself without any clock running on earth. However, by
convention the low-level way to store an absolute point in time is in
UTC, which is a timezone, because there is no way to store an absolute
point in time without a referential, much like a point in space cannot
be stored without a referential.
- DST : Daylight Save Time, makes the timezone shift in a specific
period every year in a given timezone, to make better use of longer days
for various reasons, not all timezones have DST. DST can be 1 hour or 30
min, 45 min, which makes computation difficult.
- UTC : It is NOT an “absolute timezone”, it is the wall-clock time at
0° longitude without DST, which is an arbitrary and shared human
convention. UTC is often used as the standard reference wall-clock time
for talking about absolute point in time without having to do timezone
and DST arithmetic. PostgreSQL stores everything in UTC by convention,
but outputs everything in the server’s SESSION TIMEZONE.

## How should an absolute point in time be stored ?

Since an absolute point in time is essentially distinct from its
timezone it could be stored in an absolute way, but in practice it is
impossible to store an absolute point in time without a referential. We
have to say that a rocket launched at X given time, in UTC, EST, CET,
etc. And of course, someone in China will say that it launched at 10:30,
while in San Francisco it will have launched at 19:30, but it is THE
SAME absolute point in time.

Let’s take a related example in computer science with character
encoding. If a text is stored without the associated encoding table, the
correct meaning associated to the bits stored in memory can be lost
forever. It can become impossible for a program to guess what encoding
table should be used for a given text stored as bits, thus the glitches
that appeared a lot back in the early days of internet and document
processing.

The same can happen with date time storing, if we don’t have the
timezone associated with the absolute point in time, the information of
when it absolutely happened is lost.

It is NOT necessary to store an absolute point in time in UTC, it is
more of a standard and practical wall-clock time to be associated with
an absolute point in time. But an absolute point in time MUST be store
with a timezone, with its time referential, otherwise the information of
when it absolutely happened is lost.

For example, it is easier to pass around a date as a string in UTC, like
`2024-01-02T00:00:00Z` because it allows front-end and back-end code to
“talk” in the same standard and DST-free wall-clock time, BUT it is not
necessary. Because we have date libraries that operate on the standard
ISO timezone tables, we can talk in different timezone and let the
libraries handle the conversion internally.

It is false to say that UTC is an absolute timezone or an absolute point
in time, it is just the standard, conventional time referential, because
one can perfectly store every absolute points in time in UTC+10 with a
complex DST table and have the exactly correct absolute points in time,
without any loss of information, without having any UTC+0 dates
involved.

Thus storing an absolute point in time without a timezone associated,
for example with `timestamp` PostgreSQL data type, is equivalent to
storing a wall-clock time and then throwing away voluntarily the
information that allows to know when it absolutely happened, which is a
voluntary data-loss if the code that stores and retrieves those
wall-clock points in time don’t store the associated timezone somewhere.

This is why we use `timestamptz` type in PostgreSQL, so that we make
sure that the correct absolute point in time is stored at the exact time
we send it to PostgreSQL server, no matter the front-end, back-end and
SQL server's timezone differences.

## The JavaScript Date object

The native JavaScript Date object is now officially considered legacy
([source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)),
the Date object stores an absolute point in time BUT it forces the
storage to use its execution environment timezone, and one CANNOT modify
this timezone, this is a legacy behavior.

To obtain the desired result and store an absolute point in time with an
arbitrary timezone there are several options :

- The new Temporal API that is the successor of the legacy Date object.
- Moment / Luxon / @date-fns/tz that expose objects that allow to use
any timezone to store an absolute point in time.

## How PostgreSQL stores absolute point in times

PostgreSQL stores absolute points in time internally in UTC
([source](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-INPUT-TIME-STAMPS)),
but the output date is expressed in the server’s session timezone
([source](https://www.postgresql.org/docs/current/sql-set.html)) which
can be different from UTC.

Example with the object companies in Twenty seed database, on a local
instance, with a new “datetime” custom column :

<img width="374" height="554" alt="image"
src="https://github.com/user-attachments/assets/4394cb43-d97e-4479-801d-ca068f800e39"
/>

<img width="516" height="524" alt="image"
src="https://github.com/user-attachments/assets/b652f36a-d2e2-47a4-8950-647ca688cbbd"
/>

## Why can’t I just use the JavaScript native Date object with some
manual logic ?

Because the JavaScript Date object does not allow to change its internal
timezone, the libraries that are based on it will behave on the
execution environment timezone, thus leading to bugs that appear only on
the computers of users in a timezone but not for other in another
timezone.

In our case the `react-datepicker` library forces to use the `Date`
object, thus forcing the calendar to behave in the execution environment
system timezone, which causes a lot of problems when we decide to
display the Twenty application DATE_TIME values in another timezone than
the user system one, the bugs that appear will be of the off-by-one date
class, for example clicking on 23 will select 24, thus creating an
unreliable feature for some system / application timezone combinations.

A solution could be to manually compute the difference of minutes
between the application user and the system timezones, but that’s not
reliable because of DST which makes this computation unreliable when DST
are applied at different period of the year for the two timezones.

## Why can’t I compute the timezone difference manually ?

Because of DST, the work to compute the timezone difference reliably,
not just for the usual happy path, is equivalent to developing the
internal mechanism of a date timezone library, which is equivalent to
use a library that handles timezones.

## Using `@date-fns/tz` to solve this problem

We could have used `luxon` but it has a heavy bundle size, so instead we
rely here on `@date-fns/tz` (~1kB) which gives us a `TZDate` object that
allows to use any given timezone to store an absolute point-in-time.

The solution here is to trick `react-datepicker` by shifting a Date
object by the difference of timezone between the user application
timezone and the system timezone.

Let’s take a concerte example.

System timezone : Midway,  ⇒ UTC-11:00, has no DST.

User application timezone : Auckland, NZ ⇒ UTC+13:00, has a DST.

We’ll take the NZ daylight time, so that will make a timezone difference
of 24 hours !

Let’s take an error-prone date : `2025-01-01T00:00:00` . This date is
usually a good test-case because it can generate three classes of bugs :
off-by-one day bugs, off-by-one month bugs and off-by-one year bugs, at
the same time.

Here is the absolute point in time we take expressed in the different
wall-clock time points we manipulate


Case | In system timezone ⇒ UTC-11 | In UTC | In user application
timezone ⇒ UTC+13
-- | -- | -- | --
Original date | `2024-12-31T00:00:00-11:00` | `2024-12-31T11:00:00Z` |
`2025-01-01T00:00:00+13:00`
Date shifted for react-datepicker | `2025-01-01T00:00:00-11:00` |
`2025-01-01T11:00:00Z` | `2025-01-02T00:00:00+13:00`

We can see with this table that we have the number part of the date that
is the same (`2025-01-01T00:00:00`) but with a different timezone to
“trick” `react-datepicker` and have it display the correct day in its
calendar.

You can find the code in the hooks
`useTurnPointInTimeIntoReactDatePickerShiftedDate` and
`useTurnReactDatePickerShiftedDateBackIntoPointInTime` that contain the
logic that produces the above table internally.

## Miscellaneous

Removed FormDateFieldInput and FormDateTimeFieldInput stories as they do
not behave the same depending of the execution environment and it would
be easier to put them back after having refactored FormDateFieldInput
and FormDateTimeFieldInput

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-11-01 18:31:24 +01:00
MarieandGitHub bcdc07273e Fix orderBy, groupBy, and orderByForRecords on foreignKey (#15480)
For the variables orderBy (for findMany and groupBy), groupBy (for
groupBy) and orderByForRecords (for groupBy), we were wrongfully adding
the foreign key field (eg: pointOfContact) by its joinColumnName (eg:
pointOfContactId) as the variable key (eg. `orderBy: { pointOfContactId:
"AscNullsFirst" } }`. That broke because then this key is used to
identify the field in the parsers.

This went unnoticed because this order / group option is not very
interesting as it is limited to the id for now, but it s still better to
have it work than crash!

before (on findMany)
<img width="1841" height="674" alt="flawn_order_by_pocId"
src="https://github.com/user-attachments/assets/3ccd7604-041d-4b7e-8b6a-c1d268440a45"
/>

after (on findMany)
<img width="1182" height="805" alt="image"
src="https://github.com/user-attachments/assets/e9253683-bcbe-429e-bbef-e334c7cc76af"
/>
2025-10-31 15:13:49 +01:00
MarieandGitHub 1ad8c05fbc groupBy fix + typeMapper fix (#15433)
groupBy fix: when a group's dimension value is NULL, we need to adapt
the raw sql (stage: NULL -> stage IS NULL)

typeMapper fix: a graphql type should be made non-nullable if was
indicated so + does not have a default value. our check on not having a
default value was limited to having a null defaultValue instead of
having a null or undefined defaultValue. This is a breaking change, but
all the queries that were providing a null value for these args were not
functioning anyway, and luckily in the FE we declared all queries adding
a `!` already.
2025-10-30 10:14:56 +00:00
MarieandGitHub 473efb5dc5 [groupBy] Order by within records (#15404)
Closes https://github.com/twentyhq/core-team-issues/issues/1727

<img width="1415" height="625" alt="image"
src="https://github.com/user-attachments/assets/cd132582-4a95-463c-8946-87b77670e023"
/>
2025-10-29 14:49:10 +01:00
EtienneandGitHub 0849483d2e Common - Remove feature flag (#15371)
To be done in an other PR, move graphql-query-runner handlers in
common-api-query-runner folder (+ renaming + typing improvments)


Fixes : 
- totalCount on findMany (Rest)
- getAllSelectedFields did not handle composite field (Rest)
- issue with endCursor (Rest)
- args processing for updateOne and createOne (Rest + Gql)
- fix findDuplicates (Gql)
2025-10-29 10:01:52 +00:00
EtienneandGitHub 5ae7674b9d Common - Throttle update + Metrics (#15413) 2025-10-28 19:15:18 +01:00
1590751131 Set formatted before instead of after (#15401)
Soft deletion events use before in event properties. But we set the
after instead, that only contains a few data.

Fixes https://github.com/twentyhq/twenty/issues/15120

Debugging raised an additional issue. UpdateMany, RestoreMany and
SoftDeleteMany were broken for custom objects. The `where` clause was
using "_objectName.id" instead of "objectName.id" during select. That's
why we need to use different where clause between the select of the
existing value and the actual mutation.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-28 17:13:39 +01:00
EtienneandGitHub ee6b86b1a8 Common - many fixes (#15395) 2025-10-28 13:33:41 +01:00
MarieandGitHub 0e7d18546d [groupBy] Handle relations on groupBy with records (#15379)
Following PR https://github.com/twentyhq/twenty/pull/15307
2025-10-27 15:14:02 +00:00
EtienneandGitHub 93ab1c4118 Throttling in common api (#15338) 2025-10-24 17:32:51 +02:00
MarieandGitHub d7bda9576f [groupBy] Load record within groups (without relations) (#15307)
First step of https://github.com/twentyhq/core-team-issues/issues/1726.
I will handle relations in another PR.
Another ticket is planned to allow for sorting among the records.

When querying records we have set the number of maximum groups to 50,
and of maximum records par group to 10.

<img width="1283" height="798" alt="image"
src="https://github.com/user-attachments/assets/7ffc9805-d715-4017-8030-4f3521e6f741"
/>
2025-10-24 15:30:57 +00:00
e37b8b61ba message channel change 2 (#15269)
Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-24 16:39:22 +02:00
EtienneandGitHub 10f4f1592f Common - Update one/many, restore one/many, findDuplicates & mergeMany (#15279)
closes https://github.com/twentyhq/core-team-issues/issues/1739
2025-10-24 12:41:17 +00:00
MarieandGitHub 389433a6b9 Remove groupBy feature flag (#15281) 2025-10-23 13:35:16 +02:00
EtienneandGitHub dc05f18310 Common api - Destroy and delete queries (#15177)
Done ⬇️ 
Gql : move delete and destroy logic to common
Rest : 
- rename 'delete' handler to 'destroy'
- create soft delete handlers (named 'delete') : one and many
- create destroy many handler
- update doc

--> Rest api gains NEW soft delete one/many + destroy many capabilities

closes : https://github.com/twentyhq/core-team-issues/issues/1579
2025-10-22 16:35:17 +02:00
Raphaël BosiandGitHub 1c27206b41 Use aggregate operations in the widget configuration instead of extended aggregate operations (#15248)
- Use aggregate operations in the widget configuration instead of
extended aggregate operations
- Use aggregate operation from generated graphql in the frontend
2025-10-22 14:19:02 +02:00
226cc9eb74 [Dashboards] - Min Max range on secondary axis bar charts (#15118)
video QA


https://github.com/user-attachments/assets/70c37188-2398-43de-bbf6-5882bb79940a

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-22 10:51:01 +02:00