Commit Graph
2423 Commits
Author SHA1 Message Date
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 BordeauandGitHub 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
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
0246b729af. 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 MalfaitandGitHub 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
eeegggandGitHub 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
c6ea7ae288 Allow custom fields to be editable for system objects and prevent the error for timeline activity on system object pages. (#16268)
Solves #16015 

- Added a check in `useTimelineActivities.ts` to see if the system
object page we're viewing has timelineActivity being tracked before
querying to get the activity history.
- Removed @WorkspaceIsObjectUIReadOnly decorator from system objects and
added @WorkspaceIsFieldUIReadOnly to standard and system fields to allow
custom field edits as requested in the issue.
- Did not add calendarEvents or other system objects to timelineActivity
just yet since keeping track of timeline activity for every one of them
felt counter-intuitive and bloated. In order to determine which objects
need timelineActivity, I think we need to fix the broken views of system
objects first, such as the one in the attached screenshots - a good
number of them are broken. The check I added to
`useTimelineActivities.ts` hook displays timeline activity as empty for
the time - error would not be shown as suggested by Thomas.

<img width="1062" height="858" alt="image"
src="https://github.com/user-attachments/assets/e877e0fe-b665-46e3-b785-e84f2af7f833"
/>

<br />

<img width="1061" height="858" alt="image"
src="https://github.com/user-attachments/assets/0eba8c1c-444a-4b13-beda-64b95cf39077"
/>

- Editing custom fields on calendar events (and other objects without
position fields) crashed with TypeError: Cannot convert undefined or
null to object in sortCachedObjectEdges. This happened because some
cached queries had empty orderBy arrays ([]), and the optimistic effect
tried to sort with them. Objects without position fields returned
empty orderBy arrays when there were no sorts, while objects with
position fields automatically got [{ position: 'AscNullsFirst' }].
The backend always adds { id: 'AscNullsFirst' } as a fallback, but
the frontend didn't match this. So, I added { id: 'AscNullsFirst' } to
the Frontend as default orderBy when there are no sorts and no position
field.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Apply per-field UI read-only to system/standard fields and skip
timeline activity fetching when the target object isn’t related; refine
record-table cell open/navigation behavior.
> 
> - Server: Replace object-level UI read-only with per-field
`isUIReadOnly` across many standard objects (e.g., `calendarEvent`,
`workspaceMember`, messaging, calendar, favorites, attachments,
workflows, etc.), and mirror this via `@WorkspaceIsFieldUIReadOnly` on
workspace entities.
> - Frontend: In `useTimelineActivities.ts`, check object metadata for a
relation to `timelineActivity` and `skip` the query when absent,
preventing errors on system object pages.
> - Frontend: Simplify/adjust record-table cell logic—remove unused
args, allow navigation from first column when non-empty, block editing
for read-only records (while still allowing navigation), and update
button handlers/hover styles accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
dac1262d70. 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-22 22:22:30 +00:00
Félix MalfaitandGitHub e6491d6a80 feat(i18n): fix translation QA issues and add automation (#16756)
## Summary

This PR fixes translation QA issues and adds automation to prevent
future issues.

### Translation Fixes
- Fixed **escaped Unicode sequences** in translations (e.g.,
`\u62db\u5f85` → `招待`)
- Removed **corrupted control characters** from .po files (null bytes,
invalid characters)
- Fixed **missing/incorrect placeholders** in various languages
- Deleted **35 problematic translations** via Crowdin API that had
variable mismatches

### New Scripts (in `packages/twenty-utils/`)
- `fix-crowdin-translations.ts` - Auto-fixes encoding issues and syncs
to Crowdin
- `fix-qa-issues.ts` - Fixes specific QA issues via Crowdin API
- `translation-qa-report.ts` - Generates weekly QA report from Crowdin
API

### New Workflow
- `i18n-qa-report.yaml` - Weekly workflow that creates a PR with
translation QA issues for review

### Other Changes
- Moved GitHub Actions from `.github/workflows/actions/` to
`.github/actions/`
- Fixed `date-utils.ts` to avoid nested `t` macros in plural expressions
(root cause of confusing placeholders)

### QA Status After Fixes
| Category | Count | Status |
|----------|-------|--------|
| variables | 0  | Fixed |
| tags | 1 | Minor |
| empty | 0  | Fixed |
| spaces | 127 | Low priority |
| numbers | 246 | Locale-specific |
| special_symbols | 268 | Locale-specific |
2025-12-22 17:30:46 +01:00
martmullandGitHub bb73cbc380 1774 extensibility v1 create an exhaustive documentation readme or dedicated section in twenty contributing doc (#16751)
As title

<img width="1108" height="894" alt="image"
src="https://github.com/user-attachments/assets/e2dc7e12-72e3-4ca3-ac7b-a94de547f82a"
/>
2025-12-22 15:19:11 +01:00
MarieandGitHub 50b0665c44 Enable read on replica for all (#16740)
Removing the feature flag to enable read on replica for all workspaces.
It will still be possible to toggle off the feature by removing the env
variable `PG_DATABASE_REPLICA_URL`.
2025-12-22 14:32:04 +01:00
Thomas TrompetteandGitHub 9aa77ab991 Fix composite upsert (#16718)
Doing an upsert on existing value, composite field not updated properly.

```
Input: {
  id: "08ca34fe-fc39-474f-adac-4d89f844e922",
  name: "tom",
  linkedinLink: {
    primaryLinkUrl: "https://www.linkedin.com/in/etienneyaouni1982",
    primaryLinkLabel: "etienne",
    secondaryLinks: null,
  },
}
```

Building `overwrites` for upsert forgets `linkedinLink` because column
names are not flattened yet.
We don't want to call formatData yet on the input, because this is
heavy.

Overriding `overwrites` on execute.
2025-12-19 18:28:28 +01:00
neo773andGitHub d30580fc4e remove IMAP_SMTP_CALDAV feature flag (#16695)
To be merged after 
https://github.com/twentyhq/twenty/pull/16479
https://github.com/twentyhq/twenty/pull/16694
2025-12-19 18:20:12 +01:00
Thomas TrompetteandGitHub 6355557356 [POC] Real-Time (#16633)
https://github.com/user-attachments/assets/3ad7a4f3-d08b-4a1c-b3c2-bb42ef9f3575

Real time POC.

Next steps:
- find out the correct API for subscription. Should be triggered
directly in query hooks (useFindManyRecords, useLazy...)
- improve query matching
2025-12-19 07:17:02 -10:00
b587cde216 i18n - translations (#16720)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-19 18:02:03 +01:00
abe774da15 Message folders optimization (#16479)
- Batch Gmail API calls using `googleapis-batcher` for folder processing
  - Add concurrency limit for Microsoft Graph folder processing 
- Skip IMAP folder sync when no new messages (checks UIDVALIDITY/MODSEQ)
- Refactored `syncMessageFolders` to return folder state directly,
avoiding extra DB round-trips
- Refactored `processPendingFolderActions` to reuse state instead of
querying DB again
  - Add unique index on message folders entity

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-12-19 17:04:48 +01:00
Abdullah.andGitHub 936b803cba fix: auth0/node-jws improperly verifies HMAC signature (#16712)
Resolves [Dependabot Alert
339](https://github.com/twentyhq/twenty/security/dependabot/339),
[Dependabot Alert
340](https://github.com/twentyhq/twenty/security/dependabot/340) and
[Dependabot Alert
341](https://github.com/twentyhq/twenty/security/dependabot/341).
2025-12-19 16:27:10 +01:00
Abdullah.andGitHub 27ca79be7d fix: nodemailer is vulnerable to DoS through uncontrolled recursion. (#16698)
Resolves [Dependabot Alert
331](https://github.com/twentyhq/twenty/security/dependabot/331),
[Dependabot Alert
332](https://github.com/twentyhq/twenty/security/dependabot/332),
[Dependabot Alert
349](https://github.com/twentyhq/twenty/security/dependabot/349), and
[Dependabot Alert
350](https://github.com/twentyhq/twenty/security/dependabot/350).

Updated Nodemailer and packages dependent on it to use the fixed patch
version (7.0.11).
2025-12-19 15:12:07 +01:00
Paul RastoinandGitHub 6f2ff06a35 Refactor workspace creation (#16689)
# Introduction
Created an env variable to will inject a feature flag during any new
workspace init to be created through v2
2025-12-19 12:47:31 +01:00
MarieandGitHub afaf47cdf5 Read some endpoints on replica db (behind feature flag) (#16677)
Next steps: 
- move some workers' activities to replica db
2025-12-19 10:48:18 +01:00
Paul RastoinandGitHub 8acfa60412 Fix standard agent and roles deletion command (#16686)
# Introduction
Caught red handed, introduced a failing command in
https://github.com/twentyhq/twenty/pull/16499 that was failing even in
system build which is should not
2025-12-18 19:14:24 +01:00
Baptiste DevessierandGitHub b5ebc44fee Add field widget (#16609)
- Ability to display the details of a field
- The field can be edited (relations edition will be supported later)
- For now, the widget configuration stores the name of the field instead
of its fieldMetadataId. A hook resolves the fieldMetadataId from the
list of fields and the provided name. This will be replaced once we
migrate the configuration to the backend.

## Demo


https://github.com/user-attachments/assets/ab7efbda-66b2-46c1-b641-c350977c31dd

## Remaining to do

- Edition of relations
2025-12-18 17:25:51 +00:00
Paul RastoinandGitHub 38785cd4e9 Refactor seed to use twenty-standard application (#16598)
# Introduction
In this pull-request we introduce a service dedicated to the
twenty-standard app installation, we will later be able to re-use
existing logic to be more generic and allow any app installation.
For the moment sticking to this usage
https://github.com/twentyhq/core-team-issues/issues/1995

## Encountered issues
- We decided not to migrate deprecated fields ( also they will become
custom field for any existing workspace having them in the future )
- duplicate criteria
- wrong search index declaration
- forgotten isSearchable
- Attachement seed
- Restored standardId

## Note
For the moment we're still searching through standardId for code that
run on both existing and new workspaces.
For code running on new workspace exclusively we're searching using
universalIdentifier
We will standardize universalIdentifier usage later when we've migratred
all the existing workspaces

## Workspace creation
Will handle workspace creation the same way in another PR

Related https://github.com/twentyhq/twenty/pull/15065

## TODO
- [ ] Double all frontend hardcoded queries to not refer to deprecated
fields especially attachments
2025-12-18 17:08:55 +01:00
EtienneandGitHub 65f0a5bb18 Billing - fix duplicate key value violates unique constraint "IndexOnActiveSubscriptionPerWorkspace" (#16676)
[Sentry event
example](https://twenty-v7.sentry.io/issues/6606854024/events/4a78b9d1a9d5468e897ec0f90a643112/)


fixes https://github.com/twentyhq/twenty/issues/16573
2025-12-18 15:59:07 +01:00
EtienneandGitHub e93187fded Billing - Fix subscription_schedule release (#16669)
At subscription_schedule released, a subscription_schedule.update event
is received in webhook stripe controller but not handled. [Sentry event
example

](https://twenty-v7.sentry.io/issues/6606854024/events/e737c528a55048e5981756a4fad9028f/)

Payload example ⬇️ 
```
{
  "object": {
    "id": "sub_sched_1SfeABHDlIZyMfEDBhgJUCN2",
    "object": "subscription_schedule",
     ....
    "phases": [
      {
        "add_invoice_items": [],
        "application_fee_percent": null,
        "automatic_tax": {
          "disabled_reason": null,
          "enabled": false,
          "liability": null
        },
        "billing_cycle_anchor": null,
        "billing_thresholds": {
          "amount_gte": 10000,
          "reset_billing_cycle_anchor": false
        },
        "collection_method": "charge_automatically",
        "coupon": null,
        "currency": "usd",
        "default_payment_method": null,
        "default_tax_rates": [],
        "description": null,
        "discounts": [],
        "end_date": 1768731003,
        "invoice_settings": {
          "account_tax_ids": null,
          "days_until_due": null,
          "issuer": {
            "type": "self"
          }
        },
        "items": [
          {
            "billing_thresholds": null,
            "discounts": [],
            "metadata": {},
            "plan": "price_1SChAfHDlIZyMfEDCP3pGHHv",
            "price": "price_1SChAfHDlIZyMfEDCP3pGHHv",
            "quantity": 1,
            "tax_rates": []
          },
          {
            "billing_thresholds": null,
            "discounts": [],
            "metadata": {},
            "plan": "price_1SChAaHDlIZyMfEDkGCDIIjm",
            "price": "price_1SChAaHDlIZyMfEDkGCDIIjm",
            "tax_rates": []
          }
        ],
        "metadata": {},
        "on_behalf_of": null,
        "proration_behavior": "none",
        "start_date": 1766053422,
        "transfer_data": null,
        "trial_end": null
      }
    ],
    "released_at": 1768731003,
    "released_subscription": "sub_1Sfe0oHDlIZyMfEDs1lYui6v", // here !
    "subscription": null,
    "test_clock": "clock_1SfeT1HDlIZyMfEDNnlhPmOM"
  },
  "previous_attributes": {
    "released_subscription": null,
    "subscription": "sub_1Sfe0oHDlIZyMfEDs1lYui6v"
  }
}
```

related to https://github.com/twentyhq/twenty/issues/16573
2025-12-18 15:57:56 +01:00
martmullandGitHub 52cf3775b3 Fix subscription cross tenant issue (#16670)
As title
2025-12-18 15:22:17 +01:00
df456cfa2f i18n - translations (#16666)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-18 13:01:49 +01:00
Paul RastoinandGitHub 47a8a15598 Metadata modules for PageLayoutTab PageLayoutWidget (#16662)
# Introduction
Creating dedicated folders and module for both `page-layout-tab` and
`page-layout-widget`

The addition diff with deletion is due to the module being added
2025-12-18 11:15:29 +00:00
34a1c64b7e i18n - docs translations (#16660)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-18 11:21:25 +01:00
3a1e2618e2 i18n - translations (#16657)
Created by Github action

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-18 11:01:06 +01:00
Félix MalfaitandGitHub 6682d4eb02 fix: bill all events in BillingUsageService.billUsage() instead of only the first one (#16650)
## Summary

Fixes a bug where `BillingUsageService.billUsage()` only sent the first
event from the `billingEvents` array to Stripe, silently ignoring all
subsequent events.

## Bug Description

The `billUsage()` method accepts an array of `BillingUsageEvent[]` but
was only processing `billingEvents[0]`, causing:
- Customers to be undercharged for their usage
- Revenue loss due to unbilled events
- Incorrect usage tracking

## Fix

Changed the implementation to use `Promise.all()` to send all events in
the array concurrently to Stripe.

## Before
```typescript
await this.stripeBillingMeterEventService.sendBillingMeterEvent({
  eventName: billingEvents[0].eventName,  // Only first event
  value: billingEvents[0].value,
  stripeCustomerId: workspaceStripeCustomer.stripeCustomerId,
  dimensions: billingEvents[0].dimensions,
});
```

## After
```typescript
await Promise.all(
  billingEvents.map((event) =>
    this.stripeBillingMeterEventService.sendBillingMeterEvent({
      eventName: event.eventName,
      value: event.value,
      stripeCustomerId: workspaceStripeCustomer.stripeCustomerId,
      dimensions: event.dimensions,
    }),
  ),
);
```
2025-12-18 08:38:15 +01:00
Félix MalfaitandGitHub 4e15f7fe13 Use parameterized query in getRemoteTableLocalName (#16651)
This PR updates the `isNameAvailable` function in
`getRemoteTableLocalName` to use parameterized queries instead of string
interpolation when querying the information_schema.

**Changes:**
- Replaced template literal interpolation with PostgreSQL's `$1`, `$2`
placeholder syntax
- Parameters are now passed as a separate array argument to
`dataSource.query()`

This follows best practices for database queries.
2025-12-18 08:37:43 +01:00
Félix MalfaitandGitHub 1e615f7102 fix: ensure QueryRunner is released in workspace schema operations (#16649)
## Summary

Fixes a database connection leak in `WorkspaceDataSourceService` where
`QueryRunner.release()` was not being called when schema operations
failed.

## Problem

The `createWorkspaceDBSchema` and `deleteWorkspaceDBSchema` methods use
TypeORM's QueryRunner but didn't wrap the operations in
try-catch-finally blocks. When schema operations fail (e.g., permission
denied, schema conflicts), the `QueryRunner.release()` method was never
called.

**Impact:** Failed schema operations leak database connections, which
can exhaust the connection pool and cause the application to hang or
crash under load.

## Solution

Wrap both methods in try-finally blocks to ensure
`queryRunner.release()` is always called, regardless of whether the
operation succeeds or fails.

## Changes

- `createWorkspaceDBSchema`: Wrapped in try-finally to ensure connection
release
- `deleteWorkspaceDBSchema`: Wrapped in try-finally to ensure connection
release
2025-12-18 08:26:07 +01:00
Félix MalfaitandGitHub 75ae2b401b feat: soft disable LOCAL code interpreter driver in production (#16647)
## Summary

Instead of throwing an error at server startup when LOCAL code
interpreter is configured in production, we now return a DisabledDriver
that only throws when the code interpreter is actually used.

## Changes

- Created `DisabledDriver` class that implements `CodeInterpreterDriver`
but throws an error only when `execute()` is called
- Added `DISABLED` to the `CodeInterpreterDriverType` enum
- Updated the factory to return a `DISABLED` driver config instead of
throwing when LOCAL is used in production
- Updated the module to handle the new `DISABLED` driver type

## Motivation

Many users don't need the code interpreter feature and want to deploy to
production without configuring E2B. Previously, the server would crash
at startup if `CODE_INTERPRETER_TYPE=LOCAL` was set in production.

**Before:** Server crashes at startup in production if
`CODE_INTERPRETER_TYPE=LOCAL`

**After:** Server starts fine. The error only occurs if someone actually
tries to **use** the code interpreter feature, at which point they get a
clear error message explaining how to configure E2B.
2025-12-18 08:03:13 +01:00
Abdullah.andGitHub c13b955a36 fix: hide GraphQL stack traces and messages in production (#16593)
Resolves [Code Scanning Alert
180](https://github.com/twentyhq/twenty/security/code-scanning/180).

- Normalize unexpected GraphQL errors in convertExceptionToGraphql to a
generic "Internal Server Error" instead of exposing exception.name
directly to clients.
- Only attach stack and response (original error message) in
development, so production responses don’t leak internal class names,
implementation details, or stack traces, while observability is
preserved via `ExceptionHandlerService`/Sentry.
- Keep behavior consistent with `convertHttpExceptionToGraphql`, which
also only exposes detailed response and stack information when `NODE_ENV
=== DEVELOPMENT`.
2025-12-17 18:29:40 +01:00
59d3a14922 Common API - Add tests on position field validation at creation (#16630)
Co-authored-by: guillim <guigloo@msn.com>
2025-12-17 16:02:37 +00:00
Paul RastoinandGitHub 0e6a8c04c4 Async validators and additional cache maps in v2 Builder (#16618)
# Introduction
@bosiraphael has to introduce async validators and feature flag
contextual validator
In this way in this PR we make all entity validators asyncable
Also added an `additionalCacheDataMaps` to the low level args validators
2025-12-17 15:43:25 +01:00
martmullandGitHub 1cbbd04761 Function trigger updates 2 (#16608)
- Improves route trigger job performances
- expose function params types

## Before

<img width="938" height="271" alt="image"
src="https://github.com/user-attachments/assets/5752ba64-f31d-44ed-974d-536e63458f2c"
/>


## After

<img width="1000" height="559" alt="image"
src="https://github.com/user-attachments/assets/b1f4927a-5f43-49f0-a606-244c72356772"
/>
2025-12-16 18:20:26 +01:00
38a0bba57b i18n - docs translations (#16600)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-16 15:21:59 +01:00
a6127c2128 i18n - translations (#16596)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2025-12-16 15:04:08 +01:00
Félix MalfaitandGitHub 04c596817a feat(server): enforce userFriendlyMessage on all exceptions (#16589)
## Summary
This PR enforces that all custom exceptions must provide a
`userFriendlyMessage`, ensuring end users always see readable error
messages.

## Changes

### Core Changes
- **`CustomException` simplified**: Removed the `ForceFriendlyMessage`
generic parameter - `userFriendlyMessage` is now always required
- **Type safety**: The constructor now requires `{ userFriendlyMessage:
MessageDescriptor }` (no longer optional)

### Updated Files
- **74+ exception classes** updated to provide default user-friendly
messages using Lingui `msg` macro
- Each exception class has a sensible fallback message (e.g., `msg\`An
authentication error occurred.\``)
- Exception classes that had code-specific message maps retain their
behavior

## Benefits
- **Compile-time enforcement**: Forgetting to add a user-friendly
message now causes a TypeScript error
- **Better UX**: End users always see a localized, human-readable error
message
- **Simpler API**: No more boolean generic parameter to think about

## Testing
- `npx nx run twenty-server:typecheck` passes
- `npx nx run twenty-server:lint` passes

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Enforces `userFriendlyMessage` on `CustomException` and updates all
exception classes to supply localized default messages, with
filters/tests adjusted accordingly.
> 
> - **Core**:
> - Enforce required `userFriendlyMessage` in `CustomException` (remove
optional generic; constructor now requires `{ userFriendlyMessage:
MessageDescriptor }`).
> - **Exceptions**:
> - Update ~70+ exception classes to set default localized messages via
Lingui `msg` maps and pass them in constructors (e.g., `AuthException`,
`ObjectMetadataException`, `FieldMetadataException`, etc.).
> - Add fallback messages where needed (e.g., `INTERNAL_SERVER_ERROR` or
domain-specific defaults).
> - **HTTP/GraphQL Filters**:
> - Ensure fallbacks create `UnknownException` with `msg` for
user-friendly text in REST/GraphQL exception filters.
> - **Tests**:
>   - Adjust unit tests to pass `userFriendlyMessage` to exceptions.
> - Update Jest snapshots to include `extensions.userFriendlyMessage` or
message objects where applicable.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
221004fdfc. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-12-16 14:44:27 +01:00
Abdullah.andGitHub d998e3b92c Display a CTA to view the existing duplicate when adding a duplicate email/domain. (#16483)
Closes [89](https://github.com/twentyhq/core-team-issues/issues/89)

## Problem

When users attempt to create or update a record with a duplicate value
for a unique field (e.g., duplicate email or domain name), they receive
a generic error message: "This record already exists. Please check your
data and try again." This provides no actionable way to locate and view
the existing conflicting record, forcing users to manually search for
it.

## Solution

This PR enhances duplicate key constraint error handling to
automatically detect the conflicting record and display a "View existing
record" link in the error notification. When clicked, users are
navigated directly to the existing record's detail page.

## Backend Changes

### 1. PostgreSQL Error Parsing
(`parse-postgres-constraint-error.util.ts`)
- Extracts structured information from PostgreSQL `QueryFailedError`
messages.

### 2. Conflicting Record Lookup (`find-conflicting-record.util.ts`)
- Queries the database to find the existing record with the conflicting
value

### 3. Error Handling Orchestration
(`handle-duplicate-key-error.util.ts`)
- Parses PostgreSQL error to extract column name and conflicting value
- Attempts to find the conflicting record
- Enriches `TwentyORMException` with `conflictingRecordId` and
`conflictingObjectNameSingular` if found

### 4. Exception Computation Updates (`compute-twenty-orm-exception.ts`)
- Made function `async` and added optional `entityManager` and
`internalContext` parameters
- Needed to support async database queries for conflicting record lookup

### 5. GraphQL Error Handler
(`twenty-orm-graphql-api-exception-handler.util.ts`)
- **Changes**: Enhanced `DUPLICATE_ENTRY_DETECTED` case to include
`conflictingRecordId` and `conflictingObjectNameSingular` in GraphQL
error extensions

## Frontend Changes

### 1. Error Extraction Utility
(`get-conflicting-record-from-apollo-error.util.ts`)
- Accesses GraphQL error extensions
- Validates that both `conflictingRecordId` and
`conflictingObjectNameSingular` exist and are strings
- Returns `null` if validation fails

### 2. SnackBar Enhancement (`useSnackBar.ts`)
- Extracts conflicting record info from Apollo error
- Constructs URL using `getAppPath` utility
- Adds link object to snackbar options with text "View existing record"

<img width="931" height="858" alt="image"
src="https://github.com/user-attachments/assets/28137dc7-18ab-4ffe-b669-1f2d4ec264d1"
/>
2025-12-16 14:27:34 +01:00
Paul RastoinandGitHub 75bba5a8ee Standard Agent, Role, Role target (#16499)
# Introduction
In this pullrequest have been migrated to the flat standard entities:
Role, Agent and RoleTargets.

## What happens
- Removed createStandardMorph tool util in favor of dynamic typing of
`createMorphOrRelationStandardField`
- Implemented a command to remove standard agents and their role that
has been removed in https://github.com/twentyhq/twenty/pull/16513 also
added a default role target to data manipulator role to the only
remaining agent
- Implemented an agent deleteMany service handler
2025-12-16 14:17:31 +01:00
EtienneandGitHub 0ecb60f50b E2B var env fix (#16576) 2025-12-15 18:20:22 +01:00
MarieandGitHub a0b963ef86 Remove viewGroup.fieldMetadataId (#16571)
Final step of
https://github.com/orgs/twentyhq/projects/1/views/8?pane=issue&itemId=142348748&issue=twentyhq%7Ccore-team-issues%7C1965

Removing viewGroup.fieldMetadataId.
It's already not used in FE anymore
2025-12-15 17:58:45 +01:00
martmullandGitHub e289f3056e 1895 extensibility v1 application tokens 3 (#16504)
- moves applicationRoleId to application entity
- add new `APPLICATION` FieldActorSource and `APPLICATION`
JwtTokenTypeEnum value
- create a new token with applicationId when executing a function
- when applicationId is in token, check for application.defaultRole
permissions
-use twenty-shared types in `twenty-sdk/application`
- create a new import from generate called "Twenty" that you can use
directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep
metadata or core parameter only)
- provide to serverless unique one time BEARER TOKEN to run it

Result
<img width="977" height="566" alt="image"
src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c"
/>

<img width="910" height="596" alt="image"
src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324"
/>

<img width="741" height="568" alt="image"
src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f"
/>
2025-12-15 17:44:23 +01:00
039a3d99a8 Fix APIKey typing (#16541)
Following this https://github.com/twentyhq/twenty/pull/16540
Those were not failing (yet) but were wrongly typed and error prone

---------

Co-authored-by: Guillim <guillim@users.noreply.github.com>
2025-12-15 16:05:28 +00:00
nitinandGitHub fc74a06f53 [Dashboards] add hide empty category option for pie charts (#16550)
closes
https://discord.com/channels/1130383047699738754/1445403391722520677


https://github.com/user-attachments/assets/980225a8-153e-4b49-89f7-5944227c8fdb


toggle label ie `Hide empty category` -- debatable
2025-12-15 15:40:46 +00:00
Félix MalfaitandGitHub 2e104c8e76 feat(ai): add code interpreter for AI data analysis (#16559)
## Summary

- Add code interpreter tool that enables AI to execute Python code for
data analysis, CSV processing, and chart generation
- Support for both local (development) and E2B (sandboxed production)
execution drivers
- Real-time streaming of stdout/stderr and generated files
- Frontend components for displaying code execution results with
expandable sections

## Code Quality Improvements

- Extract `getMimeType` to shared utility to reduce code duplication
between drivers
- Fix security issue: escape single quotes/backslashes in E2B driver env
variable injection
- Add `buildExecutionState` helper to reduce duplicated state object
construction
- Add `DEFAULT_CODE_INTERPRETER_TIMEOUT_MS` constant for consistency
- Fix lingui linting warning and TypeScript theme errors in frontend

## Test Plan

- [ ] Test code interpreter with local driver in development
- [ ] Test code interpreter with E2B driver in production environment
- [ ] Verify streaming output displays correctly in chat UI
- [ ] Verify generated files (charts, CSVs) are uploaded and
downloadable
- [ ] Test file upload flow (CSV, Excel) triggers code interpreter

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Updates generated i18n catalogs for Polish and pseudo-English, adding
strings for code execution/output (code interpreter) and various UI
messages, with minor text adjustments.
> 
> - **Localization**:
> - **Generated catalogs**: Refresh `locales/generated/pl-PL.ts` and
`locales/generated/pseudo-en.ts`.
> - Add strings for code execution/output (e.g., code, copy code/output,
running/waiting states, download files, generated files, Python code
execution).
> - Include new UI texts (errors, prompts, menus) and minor text
corrections.
>   - No changes to `pt-BR`; other files unchanged functionally.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
befc13d02c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-12-15 16:11:24 +01:00
Abhishek KumarandGitHub 042972d7b2 fix(workflow): line break not supported by Send Email Nodes (#16561)
Closes #16557

Tiptap Editor (which the Send Email Node uses) , creates a content json
with type 'hardBreak' for line breaks.

The was no rederer defined for this `hardBreak` node type, so the
`renderNode` function was ignoring that node (returning null).

**Fix :** Added a renderer for  `hardBreak` node type.
2025-12-15 13:24:31 +01:00
Abdullah.andGitHub cf6fb419e0 fix: ensure deactivated object records do not appear in search (#16532)
Deactivating an object still allowed querying its records in search.

<img width="1512" height="856" alt="image"
src="https://github.com/user-attachments/assets/0a1ba36f-1ecf-44d3-8e97-294bb4e81e4f"
/>

<br />
<br />

This PR introduces a minor improvement to not query records of
deactivated objects.

<img width="1512" height="856" alt="image"
src="https://github.com/user-attachments/assets/f5fd1377-2fda-4653-b3cc-30a59d52e4cf"
/>
2025-12-15 10:42:22 +01:00
EtienneandGitHub d36d0d49d1 Investigate - Slow transformRichTextV2 function (#16535)
Investigate CPU overloading ...

related to https://github.com/twentyhq/twenty/issues/15916
2025-12-12 18:39:01 +01:00