Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52f09c76b6 | ||
|
|
c2a48a1cc2 | ||
|
|
37a1d3980f | ||
|
|
05f58b2dba | ||
|
|
4e0b69eb8d | ||
|
|
99799073e9 | ||
|
|
084fa8eaba | ||
|
|
788d120b71 | ||
|
|
3128157988 | ||
|
|
068d365731 | ||
|
|
9b05e7fff4 |
@@ -3392,7 +3392,7 @@ input UpdateViewFilterGroupInput {
|
||||
input CreateViewFilterInput {
|
||||
id: UUID
|
||||
fieldMetadataId: UUID!
|
||||
operand: ViewFilterOperand = CONTAINS
|
||||
operand: ViewFilterOperand
|
||||
value: JSON!
|
||||
viewFilterGroupId: UUID
|
||||
positionInViewFilterGroup: Float
|
||||
@@ -3500,7 +3500,7 @@ input UpsertViewWidgetViewFieldInput {
|
||||
input UpsertViewWidgetViewFilterInput {
|
||||
id: UUID
|
||||
fieldMetadataId: UUID!
|
||||
operand: ViewFilterOperand = CONTAINS
|
||||
operand: ViewFilterOperand
|
||||
value: JSON!
|
||||
viewFilterGroupId: UUID
|
||||
positionInViewFilterGroup: Float
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Views
|
||||
description: Ship pre-configured saved views — column order, filters, groups — for objects in your app.
|
||||
icon: "list"
|
||||
icon: 'list'
|
||||
---
|
||||
|
||||
A **view** is a saved configuration for how records of an object are displayed: which fields appear, their order, whether they're visible, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app — typically a default index view for each custom object you create.
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
- You can also declare `filters`, `filterGroups`, `groups`, and `fieldGroups` for advanced configurations.
|
||||
- `position` controls ordering when multiple views exist for the same object.
|
||||
|
||||
## Filters
|
||||
|
||||
A view can ship with pre-applied filters. Each filter has three coordinates: the **field** being filtered, the **operand** (how to compare), and the **value** (what to compare against). All three must line up — using an operand that doesn't apply to a field type will be rejected at sync time.
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Supported operands per field type
|
||||
|
||||
| Field type | Supported operands |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> Field types with similar names can use entirely different operands — `SELECT` and `MULTI_SELECT` being a common case.
|
||||
|
||||
### Value shape per operand
|
||||
|
||||
The `value` field is always a JSON-serializable value, but its expected shape depends on the operand:
|
||||
|
||||
| Operand family | Value shape | Example |
|
||||
| ----------------------------------------------------- | ------------------------------ | ------------------------ |
|
||||
| `IS`, `IS_NOT` on `SELECT` | array of option keys (strings) | `['ACTIVE', 'PENDING']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` on `MULTI_SELECT` | array of option keys (strings) | `['TAG_A']` |
|
||||
| `IS`, `IS_NOT` on `RELATION` | array of record IDs (uuids) | `['c5a1...']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` on text-like fields | string | `'acme'` |
|
||||
| `IS`, `IS_NOT` on `NUMBER` | string (the value) | `'5'` |
|
||||
| `IS` on `RATING` / `UUID` | string (the value) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | string (the bound) | `'10'` |
|
||||
| `IS`, `IS_BEFORE`, `IS_AFTER` on `DATE` / `DATE_TIME` | ISO 8601 string | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | empty string | `''` |
|
||||
| `IS` on `BOOLEAN` | `'true'` or `'false'` | `'true'` |
|
||||
|
||||
## How views show up in the UI
|
||||
|
||||
A view by itself isn't reachable from the sidebar. To make it appear there, pair it with a [navigation menu item](/developers/extend/apps/layout/navigation-menu-items) of type `VIEW` that points at the view's `universalIdentifier`. That's the canonical pattern: every custom object typically ships a default view + a sidebar entry that opens it.
|
||||
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
* Für erweiterte Konfigurationen können Sie außerdem `filters`, `filterGroups`, `groups` und `fieldGroups` deklarieren.
|
||||
* `position` steuert die Reihenfolge, wenn mehrere Ansichten für dasselbe Objekt existieren.
|
||||
|
||||
## Filter
|
||||
|
||||
Eine Ansicht kann mit vorab angewendeten Filtern ausgeliefert werden. Jeder Filter hat drei Koordinaten: das **Feld**, das gefiltert wird, der **Operand** (wie verglichen wird) und der **Wert** (womit verglichen wird). Alle drei müssen übereinstimmen — die Verwendung eines Operanden, der nicht auf einen Feldtyp anwendbar ist, wird bei der Synchronisierung zurückgewiesen.
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Unterstützte Operanden pro Feldtyp
|
||||
|
||||
| Feldtyp | Unterstützte Operanden |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> Feldtypen mit ähnlichen Namen können völlig unterschiedliche Operanden verwenden – `SELECT` und `MULTI_SELECT` sind ein häufiges Beispiel.
|
||||
|
||||
### Wertstruktur je Operand
|
||||
|
||||
Das Feld `value` ist immer ein JSON-serialisierbarer Wert, aber die erwartete Struktur hängt vom Operanden ab:
|
||||
|
||||
| Operandenfamilie | Wertstruktur | Beispiel |
|
||||
| -------------------------------------------------------- | ------------------------------------- | ------------------------ |
|
||||
| `IS`, `IS_NOT` bei `SELECT` | Array von Optionsschlüsseln (Strings) | `['ACTIVE', 'PENDING']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` bei `MULTI_SELECT` | Array von Optionsschlüsseln (Strings) | `['TAG_A']` |
|
||||
| `IS`, `IS_NOT` bei `RELATION` | Array von Datensatz-IDs (UUIDs) | `['c5a1...']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` bei textähnlichen Feldern | Zeichenkette | `'acme'` |
|
||||
| `IS`, `IS_NOT` bei `NUMBER` | String (der Wert) | `'5'` |
|
||||
| `IS` bei `RATING` / `UUID` | String (der Wert) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | String (der Grenzwert) | `'10'` |
|
||||
| `IS`, `IS_BEFORE`, `IS_AFTER` bei `DATE` / `DATE_TIME` | ISO-8601-String | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | leerer String | `''` |
|
||||
| `IS` bei `BOOLEAN` | `'true'` oder `'false'` | `'true'` |
|
||||
|
||||
## Wie Ansichten in der UI angezeigt werden
|
||||
|
||||
Eine Ansicht für sich ist aus der Seitenleiste nicht erreichbar. Damit sie dort erscheint, verknüpfen Sie sie mit einem [Navigationsmenüeintrag](/l/de/developers/extend/apps/layout/navigation-menu-items) des Typs `VIEW`, der auf den `universalIdentifier` der Ansicht zeigt. Das ist das kanonische Muster: Jedes benutzerdefinierte Objekt liefert typischerweise eine Standardansicht plus einen Eintrag in der Seitenleiste, der sie öffnet.
|
||||
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
* Você também pode declarar `filters`, `filterGroups`, `groups` e `fieldGroups` para configurações avançadas.
|
||||
* `position` controla a ordenação quando existem várias visualizações para o mesmo objeto.
|
||||
|
||||
## Filtros
|
||||
|
||||
Uma visualização pode vir com filtros pré-aplicados. Cada filtro tem três coordenadas: o **campo** a ser filtrado, o **operador** (como comparar) e o **valor** (com o que comparar). As três precisam estar alinhadas — usar um operador que não se aplica a um tipo de campo será rejeitado no momento da sincronização.
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Operadores compatíveis por tipo de campo
|
||||
|
||||
| Tipo de campo | Operandos suportados |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> Tipos de campos com nomes semelhantes podem usar operandos completamente diferentes — `SELECT` e `MULTI_SELECT` sendo um caso comum.
|
||||
|
||||
### Formato do valor por operando
|
||||
|
||||
O campo `value` é sempre um valor serializável em JSON, mas o seu formato esperado depende do operando:
|
||||
|
||||
| Família do operando | Formato do valor | Exemplo |
|
||||
| ----------------------------------------------------- | ----------------------------------- | ------------------------ |
|
||||
| `IS`, `IS_NOT` em `SELECT` | array de chaves de opções (strings) | `['ACTIVE', 'PENDING']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` em `MULTI_SELECT` | array de chaves de opções (strings) | `['TAG_A']` |
|
||||
| `IS`, `IS_NOT` em `RELATION` | array de IDs de registros (uuids) | `['c5a1...']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` em campos de texto | string | `'acme'` |
|
||||
| `IS`, `IS_NOT` em `NUMBER` | string (o valor) | `'5'` |
|
||||
| `IS` em `RATING` / `UUID` | string (o valor) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | string (o limite) | `'10'` |
|
||||
| `IS`, `IS_BEFORE`, `IS_AFTER` em `DATE` / `DATE_TIME` | string ISO 8601 | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | string vazia | `''` |
|
||||
| `IS` em `BOOLEAN` | `'true'` ou `'false'` | `'true'` |
|
||||
|
||||
## Como as visualizações aparecem na UI
|
||||
|
||||
Uma visualização por si só não é acessível a partir da barra lateral. Para fazê-la aparecer lá, associe-a a um [item de menu de navegação](/l/pt/developers/extend/apps/layout/navigation-menu-items) do tipo `VIEW` que aponte para o `universalIdentifier` da visualização. Esse é o padrão canônico: cada objeto personalizado normalmente inclui uma visualização padrão + uma entrada na barra lateral que a abre.
|
||||
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
* Puteți declara, de asemenea, `filters`, `filterGroups`, `groups` și `fieldGroups` pentru configurații avansate.
|
||||
* `position` controlează ordonarea atunci când există mai multe vizualizări pentru același obiect.
|
||||
|
||||
## Filtre
|
||||
|
||||
O vizualizare poate include filtre aplicate în prealabil. Fiecare filtru are trei coordonate: **câmpul** care este filtrat, **operandul** (cum se compară) și **valoarea** (față de ce se compară). Toate cele trei trebuie să se potrivească — folosirea unui operand care nu se aplică unui tip de câmp va fi respinsă în timpul sincronizării.
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Operanzi acceptați pentru fiecare tip de câmp
|
||||
|
||||
| Tipul câmpului | Operanzi acceptați |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> Tipurile de câmp cu nume similare pot folosi operanzi complet diferiți — `SELECT` și `MULTI_SELECT` fiind un caz comun.
|
||||
|
||||
### Formă de valoare per operand
|
||||
|
||||
Câmpul `value` este întotdeauna o valoare serializabilă JSON, dar forma sa așteptată depinde de operand:
|
||||
|
||||
| Familie de operanzi | Formă de valoare | Exemplu |
|
||||
| ----------------------------------------------------- | ---------------------------------------------- | ------------------------ |
|
||||
| `IS`, `IS_NOT` pe `SELECT` | array de chei de opțiune (șiruri de caractere) | `['ACTIVE', 'PENDING']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` pe `MULTI_SELECT` | array de chei de opțiune (șiruri de caractere) | `['TAG_A']` |
|
||||
| `IS`, `IS_NOT` pe `RELATION` | array de ID-uri de înregistrare (uuid-uri) | `['c5a1...']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` pe câmpuri de tip text | șir | `'acme'` |
|
||||
| `IS`, `IS_NOT` pe `NUMBER` | șir de caractere (valoarea) | `'5'` |
|
||||
| `IS` pe `RATING` / `UUID` | șir de caractere (valoarea) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | șir de caractere (limita) | `'10'` |
|
||||
| `IS`, `IS_BEFORE`, `IS_AFTER` pe `DATE` / `DATE_TIME` | șir în format ISO 8601 | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | șir gol | `''` |
|
||||
| `IS` pe `BOOLEAN` | `'true'` sau `'false'` | `'true'` |
|
||||
|
||||
## Cum apar vizualizările în interfața utilizatorului
|
||||
|
||||
O vizualizare, de una singură, nu este accesibilă din bara laterală. Pentru a o face să apară acolo, asociați-o cu un [element de meniu de navigare](/l/ro/developers/extend/apps/layout/navigation-menu-items) de tip `VIEW` care indică către `universalIdentifier` al vizualizării. Acesta este modelul canonic: fiecare obiect personalizat livrează, de obicei, o vizualizare implicită + o intrare în bara laterală care o deschide.
|
||||
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
* Также вы можете определить `filters`, `filterGroups`, `groups` и `fieldGroups` для более продвинутых конфигураций.
|
||||
* `position` управляет порядком, когда для одного и того же объекта существует несколько представлений.
|
||||
|
||||
## Фильтры
|
||||
|
||||
Представление может поставляться с заранее примененными фильтрами. У каждого фильтра есть три координаты: **поле**, по которому выполняется фильтрация, **операнд** (как сравнивать) и **значение** (с чем сравнивать). Все три должны совпадать — использование операнда, который не подходит для типа поля, будет отклонено во время синхронизации.
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Поддерживаемые операнды по типу поля
|
||||
|
||||
| Тип поля | Поддерживаемые операнды |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> Типы полей с похожими названиями могут использовать совершенно разные операнды — типичный пример: `SELECT` и `MULTI_SELECT`.
|
||||
|
||||
### Форма значения для каждого операнда
|
||||
|
||||
Поле `value` всегда содержит значение, сериализуемое в JSON, но ожидаемая форма зависит от операнда:
|
||||
|
||||
| Семейство операндов | Форма значения | Пример |
|
||||
| ------------------------------------------------------ | ------------------------------------- | ------------------------ |
|
||||
| `IS`, `IS_NOT` для `SELECT` | массив ключей опций (строки) | `['ACTIVE', 'PENDING']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` для `MULTI_SELECT` | массив ключей опций (строки) | `['TAG_A']` |
|
||||
| `IS`, `IS_NOT` для `RELATION` | массив идентификаторов записей (uuid) | `['c5a1...']` |
|
||||
| `CONTAINS`, `DOES_NOT_CONTAIN` для текстовых полей | строка | `'acme'` |
|
||||
| `IS`, `IS_NOT` для `NUMBER` | строка (значение) | `'5'` |
|
||||
| `IS` для `RATING` / `UUID` | строка (значение) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | строка (граница) | `'10'` |
|
||||
| `IS`, `IS_BEFORE`, `IS_AFTER` для `DATE` / `DATE_TIME` | строка в формате ISO 8601 | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | пустая строка | `''` |
|
||||
| `IS` для `BOOLEAN` | `'true'` или `'false'` | `'true'` |
|
||||
|
||||
## Как представления отображаются в интерфейсе
|
||||
|
||||
Само по себе представление недоступно из боковой панели. Чтобы оно появилось там, свяжите его с [пунктом навигационного меню](/l/ru/developers/extend/apps/layout/navigation-menu-items) типа `VIEW`, который указывает на `universalIdentifier` представления. Это канонический шаблон: каждый пользовательский объект обычно поставляется с представлением по умолчанию и пунктом боковой панели, который его открывает.
|
||||
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
* Daha gelişmiş yapılandırmalar için `filters`, `filterGroups`, `groups` ve `fieldGroups` da tanımlayabilirsiniz.
|
||||
* `position`, aynı nesne için birden fazla görünüm olduğunda sıralamayı kontrol eder.
|
||||
|
||||
## Filtreler
|
||||
|
||||
Bir görünüm, önceden uygulanmış filtrelerle gelebilir. Her filtrenin üç koordinatı vardır: filtrelenen **alan**, **işleç** (nasıl karşılaştırılacağı) ve **değer** (neyle karşılaştırılacağı). Üçünün de hizalı olması gerekir — bir alan türüne uygulanmayan bir işlecin kullanılması, senkronizasyon sırasında reddedilir.
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### Alan türüne göre desteklenen işleçler
|
||||
|
||||
| Alan tipi | Desteklenen işleçler |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> Benzer adlara sahip alan türleri tamamen farklı işleçler kullanabilir — buna `SELECT` ve `MULTI_SELECT` yaygın bir örnektir.
|
||||
|
||||
### Her işleç için değer biçimi
|
||||
|
||||
`value` alanı her zaman JSON olarak serileştirilebilir bir değerdir, ancak beklenen biçim işlece bağlıdır:
|
||||
|
||||
| İşleç ailesi | Değer biçimi | Örnek |
|
||||
| ------------------------------------------------------------- | ------------------------------------- | ------------------------ |
|
||||
| `SELECT` üzerinde `IS`, `IS_NOT` | seçenek anahtarları dizisi (metinler) | `['ACTIVE', 'PENDING']` |
|
||||
| `MULTI_SELECT` üzerinde `CONTAINS`, `DOES_NOT_CONTAIN` | seçenek anahtarları dizisi (metinler) | `['TAG_A']` |
|
||||
| `RELATION` üzerinde `IS`, `IS_NOT` | kayıt kimlikleri dizisi (uuid'ler) | `['c5a1...']` |
|
||||
| metin benzeri alanlar üzerinde `CONTAINS`, `DOES_NOT_CONTAIN` | metin | `'acme'` |
|
||||
| `NUMBER` üzerinde `IS`, `IS_NOT` | metin (değer) | `'5'` |
|
||||
| `RATING` / `UUID` üzerinde `IS` | metin (değer) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | metin (sınır) | `'10'` |
|
||||
| `DATE` / `DATE_TIME` üzerinde `IS`, `IS_BEFORE`, `IS_AFTER` | ISO 8601 dizesi | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | boş dize | `''` |
|
||||
| `BOOLEAN` üzerinde `IS` | `'true'` veya `'false'` | `'true'` |
|
||||
|
||||
## Görünümlerin kullanıcı arayüzünde görüntülenme şekli
|
||||
|
||||
Tek başına bir görünüm, kenar çubuğundan erişilebilir değildir. Orada görünmesini sağlamak için, türü `VIEW` olan ve görünümün `universalIdentifier` değerini işaret eden bir [navigasyon menüsü öğesi](/l/tr/developers/extend/apps/layout/navigation-menu-items) ile eşleştirin. Bu, standart örüntüdür: her özel nesne genellikle varsayılan bir görünüm ve bunu açan bir kenar çubuğu girdisiyle birlikte sunulur.
|
||||
|
||||
@@ -38,6 +38,60 @@ export default defineView({
|
||||
* 你还可以声明 `filters`、`filterGroups`、`groups` 和 `fieldGroups` 以进行更高级的配置。
|
||||
* 当同一对象存在多个视图时,`position` 控制其排序。
|
||||
|
||||
## 过滤器
|
||||
|
||||
视图可以附带预先应用的过滤器。 每个过滤器有三个坐标:被筛选的**字段**、**运算符**(如何比较)以及**值**(与之比较的内容)。 这三者必须全部对齐——在同步时,使用不适用于字段类型的运算符将会被拒绝。
|
||||
|
||||
```ts
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
filters: [
|
||||
{
|
||||
universalIdentifier: '...',
|
||||
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['ACTIVE'],
|
||||
},
|
||||
],
|
||||
```
|
||||
|
||||
### 各字段类型支持的运算符
|
||||
|
||||
| 字段类型 | 受支持的运算符 |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
|
||||
| `BOOLEAN` | `IS` |
|
||||
| `UUID` | `IS` |
|
||||
| `TS_VECTOR` | `VECTOR_SEARCH` |
|
||||
|
||||
> 名称相似的字段类型可以使用完全不同的运算符 —— `SELECT` 和 `MULTI_SELECT` 就是常见情况。
|
||||
|
||||
### 每个运算符对应的值结构
|
||||
|
||||
`value` 字段始终是可序列化为 JSON 的值,但其期望的结构取决于所使用的运算符:
|
||||
|
||||
| 运算符类别 | 值结构 | 示例 |
|
||||
| ----------------------------------------------------- | -------------------- | ------------------------ |
|
||||
| `SELECT` 上的 `IS`, `IS_NOT` | 选项键(字符串)数组 | `['ACTIVE', 'PENDING']` |
|
||||
| `MULTI_SELECT` 上的 `CONTAINS`, `DOES_NOT_CONTAIN` | 选项键(字符串)数组 | `['TAG_A']` |
|
||||
| `RELATION` 上的 `IS`, `IS_NOT` | 记录 ID(uuid)数组 | `['c5a1...']` |
|
||||
| 文本类字段上的 `CONTAINS`, `DOES_NOT_CONTAIN` | 字符串 | `'acme'` |
|
||||
| `NUMBER` 上的 `IS`, `IS_NOT` | 字符串(该值) | `'5'` |
|
||||
| `RATING` / `UUID` 上的 `IS` | 字符串(该值) | `'5'` |
|
||||
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | 字符串(边界值) | `'10'` |
|
||||
| `DATE` / `DATE_TIME` 上的 `IS`, `IS_BEFORE`, `IS_AFTER` | ISO 8601 字符串 | `'2025-01-01T00:00:00Z'` |
|
||||
| `IS_EMPTY`, `IS_NOT_EMPTY` | 空字符串 | `''` |
|
||||
| `BOOLEAN` 上的 `IS` | `'true'` 或 `'false'` | `'true'` |
|
||||
|
||||
## 视图在 UI 中如何显示
|
||||
|
||||
单独一个视图无法从侧边栏访问。 要让它显示在侧边栏中,请将其与类型为 `VIEW`、指向该视图 `universalIdentifier` 的[导航菜单项](/l/zh/developers/extend/apps/layout/navigation-menu-items)配对。 这是规范用法:每个自定义对象通常都会提供一个默认视图,以及一个在侧边栏中打开该视图的条目。
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Teken 'n nuwe transaksie aan (maatskappy, bedrag, stadium, verwagte slui
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Teken uit"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "سجّل صفقة جديدة (الشركة، المبلغ، المرحل
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "تسجيل الخروج"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registra una nova oportunitat (empresa, import, etapa, tancament previst
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Tancar sessió"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Zaznamenejte nový obchod (společnost, částka, fáze, očekávané uz
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Odhlásit se"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registrer en ny aftale (virksomhed, beløb, fase, forventet lukkedato).
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Log ud"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Einen neuen Deal erfassen (Unternehmen, Betrag, Phase, voraussichtlicher
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Abmelden"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Καταχωρίστε μια νέα συμφωνία (εταιρεία,
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Αποσύνδεση"
|
||||
|
||||
|
||||
@@ -9479,7 +9479,7 @@ msgstr "Log a new deal (company, amount, stage, expected close). Details: "
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Log out"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registra una nueva oportunidad (empresa, importe, etapa, fecha de cierre
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Cerrar sesión"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Kirjaa uusi diili (yritys, summa, vaihe, odotettu päättymispäivä). T
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Kirjaudu ulos"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Enregistrer une nouvelle affaire (entreprise, montant, étape, clôture
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Déconnexion"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "רשום עסקה חדשה (חברה, סכום, שלב, תאריך סג
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "התנתק"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Rögzítsen egy új ügyletet (vállalat, összeg, szakasz, várható le
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Kijelentkezés"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registra una nuova trattativa (azienda, importo, fase, chiusura prevista
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Disconnetti"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "新しい案件を記録(会社、金額、ステージ、予定クロ
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "ログアウト"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "새 거래를 기록하세요 (회사, 금액, 단계, 예상 종결일)
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "로그아웃"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registreer een nieuwe deal (bedrijf, bedrag, fase, verwachte sluitingsda
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Uitloggen"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Loggfør en ny avtale (selskap, beløp, fase, forventet avslutning). Det
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Logg ut"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Zarejestruj nową transakcję (firma, kwota, etap, oczekiwana data zamkn
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Wyloguj się"
|
||||
|
||||
|
||||
@@ -9456,7 +9456,7 @@ msgstr ""
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registre um novo negócio (empresa, valor, etapa, fechamento previsto).
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Sair"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registar um novo negócio (empresa, montante, fase, fecho previsto). Det
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Terminar sessão"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Înregistrați o tranzacție nouă (companie, sumă, etapă, închidere
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Deconectare"
|
||||
|
||||
|
||||
Binary file not shown.
@@ -9461,7 +9461,7 @@ msgstr "Евидентирајте нови договор (компанија,
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Одјавите се"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Registrera en ny affär (företag, belopp, steg, förväntat avslut). De
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Logga ut"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Yeni bir anlaşma kaydedin (şirket, tutar, aşama, beklenen kapanış).
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Oturumu kapat"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Зареєструйте нову угоду (компанія, сума
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Вийти"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "Ghi nhận một thương vụ mới (công ty, số tiền, giai đoạ
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "Đăng xuất"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "记录一笔新交易 (公司、金额、阶段、预计成交日期)。
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "退出登录"
|
||||
|
||||
|
||||
@@ -9461,7 +9461,7 @@ msgstr "記錄一筆新交易(公司、金額、階段、預計結案)。詳
|
||||
#. js-lingui-id: FgAxTj
|
||||
#: src/pages/onboarding/internal/ChooseYourPlanContent.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx
|
||||
#: src/modules/auth/sign-in-up/components/WorkspaceSelectionFooter.tsx
|
||||
msgid "Log out"
|
||||
msgstr "登出"
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const StyledOnboardingContentContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
min-width: 240px;
|
||||
`;
|
||||
+5
-25
@@ -6,7 +6,7 @@ import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { FormProvider } from 'react-hook-form';
|
||||
import { ClickToActionLink, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { StyledOnboardingContentContainer } from '@/auth/components/StyledOnboardingContentContainer';
|
||||
import { SignInUpWithCredentials } from '@/auth/sign-in-up/components/internal/SignInUpWithCredentials';
|
||||
import { SignInUpWithGoogle } from '@/auth/sign-in-up/components/internal/SignInUpWithGoogle';
|
||||
import { SignInUpWithMicrosoft } from '@/auth/sign-in-up/components/internal/SignInUpWithMicrosoft';
|
||||
@@ -23,7 +23,6 @@ import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
|
||||
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useContext } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
@@ -35,20 +34,12 @@ import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type AvailableWorkspace } from '~/generated-metadata/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
const StyledContentContainer = styled(motion.div)`
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
min-width: 200px;
|
||||
`;
|
||||
|
||||
const StyledWorkspaceContainer = styled.div`
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border: 1px solid ${themeCssVariables.border.color.light};
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
@@ -123,11 +114,6 @@ const StyledChevronIcon = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledActionLinkContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledForgotPasswordLinkContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -140,7 +126,6 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
const isDDLLocked = useAtomStateValue(isDDLLockedState);
|
||||
const signInUpStep = useAtomStateValue(signInUpStepState);
|
||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||
const { signOut } = useAuth();
|
||||
|
||||
const { createWorkspace } = useSignUpInNewWorkspace();
|
||||
const availableWorkspaces = useAtomStateValue(availableWorkspacesState);
|
||||
@@ -169,7 +154,7 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
return (
|
||||
<>
|
||||
{signInUpStep === SignInUpStep.WorkspaceSelection && (
|
||||
<>
|
||||
<StyledOnboardingContentContainer>
|
||||
<StyledWorkspaceContainer>
|
||||
{[
|
||||
...availableWorkspaces.availableWorkspacesForSignIn,
|
||||
@@ -224,15 +209,10 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
</StyledWorkspaceItem>
|
||||
)}
|
||||
</StyledWorkspaceContainer>
|
||||
<StyledActionLinkContainer>
|
||||
<ClickToActionLink onClick={signOut}>
|
||||
<Trans>Log out</Trans>
|
||||
</ClickToActionLink>
|
||||
</StyledActionLinkContainer>
|
||||
</>
|
||||
</StyledOnboardingContentContainer>
|
||||
)}
|
||||
{signInUpStep !== SignInUpStep.WorkspaceSelection && (
|
||||
<StyledContentContainer>
|
||||
<StyledOnboardingContentContainer>
|
||||
{authProviders.google && (
|
||||
<SignInUpWithGoogle
|
||||
action="list-available-workspaces"
|
||||
@@ -261,7 +241,7 @@ export const SignInUpGlobalScopeForm = () => {
|
||||
</ClickToActionLink>
|
||||
</StyledForgotPasswordLinkContainer>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
</StyledOnboardingContentContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
+3
-10
@@ -1,3 +1,4 @@
|
||||
import { StyledOnboardingContentContainer } from '@/auth/components/StyledOnboardingContentContainer';
|
||||
import { SignInUpWithCredentials } from '@/auth/sign-in-up/components/internal/SignInUpWithCredentials';
|
||||
import { SignInUpWithGoogle } from '@/auth/sign-in-up/components/internal/SignInUpWithGoogle';
|
||||
import { SignInUpWithMicrosoft } from '@/auth/sign-in-up/components/internal/SignInUpWithMicrosoft';
|
||||
@@ -9,20 +10,12 @@ import { useWorkspaceBypass } from '@/auth/sign-in-up/hooks/useWorkspaceBypass';
|
||||
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
||||
import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { FormProvider } from 'react-hook-form';
|
||||
import { HorizontalSeparator } from 'twenty-ui/display';
|
||||
import { ClickToActionLink } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
min-width: 200px;
|
||||
`;
|
||||
|
||||
export const SignInUpWorkspaceScopeForm = () => {
|
||||
const workspaceAuthProviders = useAtomStateValue(workspaceAuthProvidersState);
|
||||
const workspaceAuthBypassProviders = useAtomStateValue(
|
||||
@@ -50,7 +43,7 @@ export const SignInUpWorkspaceScopeForm = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
<StyledOnboardingContentContainer>
|
||||
{providers.google && <SignInUpWithGoogle action="join-workspace" />}
|
||||
|
||||
{providers.microsoft && (
|
||||
@@ -71,7 +64,7 @@ export const SignInUpWorkspaceScopeForm = () => {
|
||||
<SignInUpWithCredentials />
|
||||
</FormProvider>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
</StyledOnboardingContentContainer>
|
||||
{signInUpStep === SignInUpStep.Password && (
|
||||
<ClickToActionLink
|
||||
onClick={handleResetPassword(form.getValues('email'))}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { ClickToActionLink } from 'twenty-ui/navigation';
|
||||
|
||||
export const WorkspaceSelectionFooter = () => {
|
||||
const { signOut } = useAuth();
|
||||
|
||||
return (
|
||||
<ClickToActionLink onClick={signOut}>
|
||||
<Trans>Log out</Trans>
|
||||
</ClickToActionLink>
|
||||
);
|
||||
};
|
||||
+3
-9
@@ -1,22 +1,16 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { StyledOnboardingContentContainer } from '@/auth/components/StyledOnboardingContentContainer';
|
||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { guessSSOIdentityProviderIconByUrl } from '@/settings/security/utils/guessSSOIdentityProviderIconByUrl';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import React from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { HorizontalSeparator } from 'twenty-ui/display';
|
||||
import { MainButton } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
export const SignInUpSSOIdentityProviderSelection = () => {
|
||||
const workspaceAuthProviders = useAtomStateValue(workspaceAuthProvidersState);
|
||||
|
||||
@@ -24,7 +18,7 @@ export const SignInUpSSOIdentityProviderSelection = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
<StyledOnboardingContentContainer>
|
||||
{isDefined(workspaceAuthProviders?.sso) &&
|
||||
workspaceAuthProviders?.sso.map((idp) => (
|
||||
<React.Fragment key={idp.id}>
|
||||
@@ -37,7 +31,7 @@ export const SignInUpSSOIdentityProviderSelection = () => {
|
||||
<HorizontalSeparator visible={false} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledContentContainer>
|
||||
</StyledOnboardingContentContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { type FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||
import {
|
||||
type FilterableAndTSVectorFieldType,
|
||||
type ViewFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
import { type FILTER_OPERANDS_MAP } from 'twenty-shared/utils';
|
||||
|
||||
// RLS-specific: references a workspace member field for dynamic "Me" comparisons
|
||||
export type RLSDynamicValue = {
|
||||
|
||||
+10
-222
@@ -1,230 +1,18 @@
|
||||
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
|
||||
import { isFilterOnActorWorkspaceMemberSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorWorkspaceMemberSubField';
|
||||
import { type CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ViewFilterOperand as RecordFilterOperand,
|
||||
type FilterableAndTSVectorFieldType,
|
||||
type FilterableFieldType,
|
||||
type ViewFilterOperand as RecordFilterOperand,
|
||||
} from 'twenty-shared/types';
|
||||
import { assertUnreachable, isExpectedSubFieldName } from 'twenty-shared/utils';
|
||||
|
||||
export type GetRecordFilterOperandsParams = {
|
||||
filterType: FilterableAndTSVectorFieldType;
|
||||
subFieldName?: string | null | undefined;
|
||||
};
|
||||
|
||||
const emptyOperands = [
|
||||
RecordFilterOperand.IS_EMPTY,
|
||||
RecordFilterOperand.IS_NOT_EMPTY,
|
||||
] as const;
|
||||
|
||||
const relationOperands = [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
] as const;
|
||||
|
||||
type FilterOperandMap = {
|
||||
[K in FilterableAndTSVectorFieldType]: readonly RecordFilterOperand[];
|
||||
};
|
||||
|
||||
// TODO: we would need to refactor the typing of SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS first
|
||||
// with types like FieldCurrencyValue being derived from a central constant value and not being created like that
|
||||
// in order to narrow down the possible subfield names for each field type
|
||||
type CompositeFieldFilterOperandMap = {
|
||||
[K in FilterableFieldType]: Partial<{
|
||||
[S in CompositeFieldSubFieldName]: readonly RecordFilterOperand[];
|
||||
}>;
|
||||
};
|
||||
|
||||
export const FILTER_OPERANDS_MAP = {
|
||||
TEXT: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
EMAILS: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
FULL_NAME: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
ADDRESS: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
LINKS: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
PHONES: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
CURRENCY: [
|
||||
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
RecordFilterOperand.LESS_THAN_OR_EQUAL,
|
||||
...emptyOperands,
|
||||
],
|
||||
NUMBER: [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
RecordFilterOperand.LESS_THAN_OR_EQUAL,
|
||||
...emptyOperands,
|
||||
],
|
||||
RAW_JSON: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
FILES: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
DATE_TIME: [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_RELATIVE,
|
||||
RecordFilterOperand.IS_IN_PAST,
|
||||
RecordFilterOperand.IS_IN_FUTURE,
|
||||
RecordFilterOperand.IS_TODAY,
|
||||
RecordFilterOperand.IS_BEFORE,
|
||||
RecordFilterOperand.IS_AFTER,
|
||||
...emptyOperands,
|
||||
],
|
||||
DATE: [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_RELATIVE,
|
||||
RecordFilterOperand.IS_IN_PAST,
|
||||
RecordFilterOperand.IS_IN_FUTURE,
|
||||
RecordFilterOperand.IS_TODAY,
|
||||
RecordFilterOperand.IS_BEFORE,
|
||||
RecordFilterOperand.IS_AFTER,
|
||||
...emptyOperands,
|
||||
],
|
||||
RATING: [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
RecordFilterOperand.LESS_THAN_OR_EQUAL,
|
||||
...emptyOperands,
|
||||
],
|
||||
RELATION: [...relationOperands, ...emptyOperands],
|
||||
MULTI_SELECT: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
SELECT: [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
...emptyOperands,
|
||||
],
|
||||
ACTOR: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
ARRAY: [
|
||||
RecordFilterOperand.CONTAINS,
|
||||
RecordFilterOperand.DOES_NOT_CONTAIN,
|
||||
...emptyOperands,
|
||||
],
|
||||
BOOLEAN: [RecordFilterOperand.IS],
|
||||
TS_VECTOR: [RecordFilterOperand.VECTOR_SEARCH],
|
||||
UUID: [RecordFilterOperand.IS, RecordFilterOperand.IS_NOT, ...emptyOperands],
|
||||
} as const satisfies FilterOperandMap;
|
||||
|
||||
export const COMPOSITE_FIELD_FILTER_OPERANDS_MAP = {
|
||||
CURRENCY: {
|
||||
currencyCode: [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
...emptyOperands,
|
||||
],
|
||||
amountMicros: [
|
||||
RecordFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
RecordFilterOperand.LESS_THAN_OR_EQUAL,
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
...emptyOperands,
|
||||
],
|
||||
},
|
||||
} as const satisfies Partial<CompositeFieldFilterOperandMap>;
|
||||
import { getFilterOperandsForFilterableFieldType } from 'twenty-shared/utils';
|
||||
|
||||
export const getRecordFilterOperands = ({
|
||||
filterType,
|
||||
subFieldName,
|
||||
}: GetRecordFilterOperandsParams): readonly RecordFilterOperand[] => {
|
||||
switch (filterType) {
|
||||
case 'TEXT':
|
||||
case 'EMAILS':
|
||||
case 'FULL_NAME':
|
||||
case 'ADDRESS':
|
||||
case 'LINKS':
|
||||
case 'PHONES':
|
||||
return FILTER_OPERANDS_MAP.TEXT;
|
||||
case 'CURRENCY': {
|
||||
if (
|
||||
isExpectedSubFieldName(
|
||||
FieldMetadataType.CURRENCY,
|
||||
'currencyCode',
|
||||
subFieldName,
|
||||
) === true
|
||||
) {
|
||||
return COMPOSITE_FIELD_FILTER_OPERANDS_MAP.CURRENCY.currencyCode;
|
||||
} else {
|
||||
return COMPOSITE_FIELD_FILTER_OPERANDS_MAP.CURRENCY.amountMicros;
|
||||
}
|
||||
}
|
||||
case 'NUMBER':
|
||||
return FILTER_OPERANDS_MAP.NUMBER;
|
||||
case 'RAW_JSON':
|
||||
return FILTER_OPERANDS_MAP.RAW_JSON;
|
||||
case 'FILES':
|
||||
return FILTER_OPERANDS_MAP.FILES;
|
||||
case 'DATE_TIME':
|
||||
case 'DATE':
|
||||
return FILTER_OPERANDS_MAP.DATE_TIME;
|
||||
case 'RATING':
|
||||
return FILTER_OPERANDS_MAP.RATING;
|
||||
case 'RELATION':
|
||||
return FILTER_OPERANDS_MAP.RELATION;
|
||||
case 'MULTI_SELECT':
|
||||
return FILTER_OPERANDS_MAP.MULTI_SELECT;
|
||||
case 'SELECT':
|
||||
return FILTER_OPERANDS_MAP.SELECT;
|
||||
case 'ACTOR': {
|
||||
if (
|
||||
isFilterOnActorSourceSubField(subFieldName) ||
|
||||
isFilterOnActorWorkspaceMemberSubField(subFieldName)
|
||||
) {
|
||||
return [
|
||||
RecordFilterOperand.IS,
|
||||
RecordFilterOperand.IS_NOT,
|
||||
...emptyOperands,
|
||||
];
|
||||
}
|
||||
|
||||
return FILTER_OPERANDS_MAP.ACTOR;
|
||||
}
|
||||
case 'ARRAY':
|
||||
return FILTER_OPERANDS_MAP.ARRAY;
|
||||
case 'BOOLEAN':
|
||||
return FILTER_OPERANDS_MAP.BOOLEAN;
|
||||
case 'TS_VECTOR':
|
||||
return FILTER_OPERANDS_MAP.TS_VECTOR;
|
||||
case 'UUID':
|
||||
return FILTER_OPERANDS_MAP.UUID;
|
||||
default:
|
||||
assertUnreachable(filterType, `Unknown filter type ${filterType}`);
|
||||
}
|
||||
}: {
|
||||
filterType: FilterableAndTSVectorFieldType;
|
||||
subFieldName?: string | null | undefined;
|
||||
}): readonly RecordFilterOperand[] => {
|
||||
return getFilterOperandsForFilterableFieldType({
|
||||
filterType,
|
||||
subFieldName,
|
||||
});
|
||||
};
|
||||
|
||||
+6
-5
@@ -5,10 +5,7 @@ import {
|
||||
type RecordFilter,
|
||||
type RecordFilterToRecordInputOperand,
|
||||
} from '@/object-record/record-filter/types/RecordFilter';
|
||||
import {
|
||||
FILTER_OPERANDS_MAP,
|
||||
getRecordFilterOperands,
|
||||
} from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { COMPOSITE_FIELD_TYPE_SUB_FIELDS_NAMES } from 'twenty-shared/constants';
|
||||
import {
|
||||
compositeTypeDefinitions,
|
||||
@@ -16,7 +13,11 @@ import {
|
||||
ViewFilterOperand,
|
||||
type FieldMetadataOptions,
|
||||
} from 'twenty-shared/types';
|
||||
import { assertUnreachable, parseJson } from 'twenty-shared/utils';
|
||||
import {
|
||||
assertUnreachable,
|
||||
FILTER_OPERANDS_MAP,
|
||||
parseJson,
|
||||
} from 'twenty-shared/utils';
|
||||
import { RelationType } from '~/generated-metadata/graphql';
|
||||
import { convertCurrencyAmountToCurrencyMicros } from '~/utils/convertCurrencyToCurrencyMicros';
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
import {
|
||||
COMPOSITE_FIELD_FILTER_OPERANDS_MAP,
|
||||
FILTER_OPERANDS_MAP,
|
||||
} from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
const defaultOperands = [
|
||||
ViewFilterOperand.IS,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Logo } from '@/auth/components/Logo';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
|
||||
import { StyledOnboardingContentContainer } from '@/auth/components/StyledOnboardingContentContainer';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||
@@ -61,12 +62,6 @@ const StyledMainContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[8]};
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
width: 200px;
|
||||
`;
|
||||
|
||||
const StyledForm = styled.form`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -229,7 +224,7 @@ export const PasswordReset = () => {
|
||||
/>
|
||||
</AnimatedEaseIn>
|
||||
<Title animate>{passwordActionLabel}</Title>
|
||||
<StyledContentContainer>
|
||||
<StyledOnboardingContentContainer>
|
||||
{!email ? (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.quaternary}
|
||||
@@ -311,7 +306,7 @@ export const PasswordReset = () => {
|
||||
</StyledMainButtonContainer>
|
||||
</StyledForm>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
</StyledOnboardingContentContainer>
|
||||
</StyledMainContainer>
|
||||
</ModalContent>
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ import { EmailVerificationSent } from '@/auth/sign-in-up/components/EmailVerific
|
||||
import { FooterNote } from '@/auth/sign-in-up/components/FooterNote';
|
||||
import { SignInUpGlobalScopeForm } from '@/auth/sign-in-up/components/SignInUpGlobalScopeForm';
|
||||
import { SignInUpWorkspaceScopeForm } from '@/auth/sign-in-up/components/SignInUpWorkspaceScopeForm';
|
||||
import { WorkspaceSelectionFooter } from '@/auth/sign-in-up/components/WorkspaceSelectionFooter';
|
||||
import { SignInUpSSOIdentityProviderSelection } from '@/auth/sign-in-up/components/internal/SignInUpSSOIdentityProviderSelection';
|
||||
import { SignInUpWorkspaceScopeFormEffect } from '@/auth/sign-in-up/components/internal/SignInUpWorkspaceScopeFormEffect';
|
||||
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
|
||||
@@ -71,6 +72,9 @@ const StandardContent = ({
|
||||
</AnimatedEaseIn>
|
||||
<Title animate>{title}</Title>
|
||||
{signInUpForm}
|
||||
{signInUpStep === SignInUpStep.WorkspaceSelection && (
|
||||
<WorkspaceSelectionFooter />
|
||||
)}
|
||||
{![
|
||||
SignInUpStep.Password,
|
||||
SignInUpStep.TwoFactorAuthenticationProvision,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { extractManifestFromFile } from '@/cli/utilities/build/manifest/manifest
|
||||
import { addMissingFieldOptionIds } from '@/cli/utilities/build/manifest/utils/add-missing-field-option-ids';
|
||||
import { fromRoleConfigToRoleManifest } from '@/cli/utilities/build/manifest/utils/from-role-config-to-role-manifest';
|
||||
import { getDefaultFieldsInObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-fields-in-object-fields';
|
||||
import { validateViewFilterOperands } from '@/cli/utilities/build/manifest/utils/validate-view-filter-operands';
|
||||
import { type ApplicationConfig, type LogicFunctionConfig } from '@/sdk/define';
|
||||
import { type CommandMenuItemConfig } from '@/sdk/define/command-menu-items/command-menu-item-config';
|
||||
import { type FrontComponentConfig } from '@/sdk/define/front-component/front-component-config';
|
||||
@@ -488,6 +489,14 @@ export const buildManifest = async (
|
||||
);
|
||||
}
|
||||
|
||||
errors.push(
|
||||
...validateViewFilterOperands({
|
||||
views,
|
||||
objects,
|
||||
fields,
|
||||
}),
|
||||
);
|
||||
|
||||
const application: ApplicationManifest | undefined =
|
||||
applicationConfig && resolvedDefaultRoleUniversalIdentifier
|
||||
? {
|
||||
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
import { validateViewFilterOperands } from '@/cli/utilities/build/manifest/utils/validate-view-filter-operands';
|
||||
import {
|
||||
type FieldManifest,
|
||||
type ObjectFieldManifest,
|
||||
type ObjectManifest,
|
||||
type ViewManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ViewFilterOperand,
|
||||
ViewType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
const buildField = <T extends FieldMetadataType>({
|
||||
universalIdentifier,
|
||||
type,
|
||||
name = 'field',
|
||||
label = 'Field',
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
type: T;
|
||||
name?: string;
|
||||
label?: string;
|
||||
}): FieldManifest<T> =>
|
||||
({
|
||||
universalIdentifier,
|
||||
objectUniversalIdentifier: 'object-uid',
|
||||
name,
|
||||
label,
|
||||
type,
|
||||
}) as FieldManifest<T>;
|
||||
|
||||
const buildRelationField = ({
|
||||
universalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier,
|
||||
name = 'relation',
|
||||
label = 'Relation',
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
relationTargetFieldMetadataUniversalIdentifier: string;
|
||||
name?: string;
|
||||
label?: string;
|
||||
}): FieldManifest<FieldMetadataType.RELATION> =>
|
||||
({
|
||||
universalIdentifier,
|
||||
objectUniversalIdentifier: 'object-uid',
|
||||
name,
|
||||
label,
|
||||
type: FieldMetadataType.RELATION,
|
||||
relationTargetFieldMetadataUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier: 'target-object-uid',
|
||||
universalSettings: {},
|
||||
}) as FieldManifest<FieldMetadataType.RELATION>;
|
||||
|
||||
const buildView = (filters: ViewManifest['filters']): ViewManifest => ({
|
||||
universalIdentifier: 'view-uid',
|
||||
name: 'View',
|
||||
objectUniversalIdentifier: 'object-uid',
|
||||
type: ViewType.TABLE,
|
||||
filters,
|
||||
});
|
||||
|
||||
const buildObject = (fields: ObjectFieldManifest[]): ObjectManifest =>
|
||||
({
|
||||
universalIdentifier: 'object-uid',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
labelSingular: 'Company',
|
||||
labelPlural: 'Companies',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
fields[0]?.universalIdentifier ?? '',
|
||||
fields,
|
||||
}) as ObjectManifest;
|
||||
|
||||
describe('validateViewFilterOperands', () => {
|
||||
it('should return no errors when operand is valid for the field type', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'field-uid',
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
value: 'acme',
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [],
|
||||
fields: [
|
||||
buildField({
|
||||
universalIdentifier: 'field-uid',
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'company',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an error when operand is invalid for the field type', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'field-uid',
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
value: '5',
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [],
|
||||
fields: [
|
||||
buildField({
|
||||
universalIdentifier: 'field-uid',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
name: 'rating',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toContain('CONTAINS');
|
||||
expect(errors[0]).toContain('rating');
|
||||
expect(errors[0]).toContain('NUMBER');
|
||||
});
|
||||
|
||||
it('should resolve relation fields against their target field type', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'relation-field-uid',
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['target-record-uid'],
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [],
|
||||
fields: [
|
||||
buildRelationField({
|
||||
universalIdentifier: 'relation-field-uid',
|
||||
relationTargetFieldMetadataUniversalIdentifier: 'target-field-uid',
|
||||
name: 'company',
|
||||
}),
|
||||
buildField({
|
||||
universalIdentifier: 'target-field-uid',
|
||||
type: FieldMetadataType.UUID,
|
||||
name: 'id',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should fallback to RELATION operands when relation target field is missing', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'relation-field-uid',
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: ['target-record-uid'],
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [],
|
||||
fields: [
|
||||
buildRelationField({
|
||||
universalIdentifier: 'relation-field-uid',
|
||||
relationTargetFieldMetadataUniversalIdentifier: 'missing-target-uid',
|
||||
name: 'company',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should look up fields nested under objects', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'field-uid',
|
||||
operand: ViewFilterOperand.GREATER_THAN_OR_EQUAL,
|
||||
value: '5',
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [
|
||||
buildObject([
|
||||
buildField({
|
||||
universalIdentifier: 'field-uid',
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'company',
|
||||
}),
|
||||
]),
|
||||
],
|
||||
fields: [],
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toContain('GREATER_THAN_OR_EQUAL');
|
||||
expect(errors[0]).toContain('TEXT');
|
||||
});
|
||||
|
||||
it('should skip filters that reference unknown fields', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'unknown-field-uid',
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
value: 'acme',
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [],
|
||||
fields: [],
|
||||
});
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should respect CURRENCY subFieldName when checking operands', () => {
|
||||
const errors = validateViewFilterOperands({
|
||||
views: [
|
||||
buildView([
|
||||
{
|
||||
universalIdentifier: 'filter-uid',
|
||||
fieldMetadataUniversalIdentifier: 'amount-uid',
|
||||
operand: ViewFilterOperand.CONTAINS,
|
||||
value: 'USD',
|
||||
subFieldName: 'currencyCode',
|
||||
},
|
||||
]),
|
||||
],
|
||||
objects: [],
|
||||
fields: [
|
||||
buildField({
|
||||
universalIdentifier: 'amount-uid',
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
name: 'amount',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toContain('CONTAINS');
|
||||
expect(errors[0]).toContain('CURRENCY');
|
||||
});
|
||||
|
||||
it('should return an empty array when no views are defined', () => {
|
||||
expect(
|
||||
validateViewFilterOperands({ views: [], objects: [], fields: [] }),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
type FieldManifest,
|
||||
type ObjectFieldManifest,
|
||||
type ObjectManifest,
|
||||
type ViewManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
type FilterableAndTSVectorFieldType,
|
||||
} from 'twenty-shared/types';
|
||||
import {
|
||||
FILTER_OPERANDS_MAP,
|
||||
getFilterOperandsForFilterableFieldType,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
export const validateViewFilterOperands = ({
|
||||
views,
|
||||
objects,
|
||||
fields,
|
||||
}: {
|
||||
views: ViewManifest[];
|
||||
objects: ObjectManifest[];
|
||||
fields: FieldManifest[];
|
||||
}): string[] => {
|
||||
const errors: string[] = [];
|
||||
|
||||
const fieldByUniversalIdentifier = new Map<
|
||||
string,
|
||||
FieldManifest | ObjectFieldManifest
|
||||
>();
|
||||
for (const field of fields) {
|
||||
fieldByUniversalIdentifier.set(field.universalIdentifier, field);
|
||||
}
|
||||
for (const object of objects) {
|
||||
for (const objectField of object.fields) {
|
||||
fieldByUniversalIdentifier.set(
|
||||
objectField.universalIdentifier,
|
||||
objectField,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const view of views) {
|
||||
for (const filter of view.filters ?? []) {
|
||||
const referencedField = fieldByUniversalIdentifier.get(
|
||||
filter.fieldMetadataUniversalIdentifier,
|
||||
);
|
||||
if (!isDefined(referencedField)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const effectiveFieldType: FieldMetadataType =
|
||||
referencedField.type === FieldMetadataType.RELATION
|
||||
? (fieldByUniversalIdentifier.get(
|
||||
referencedField.relationTargetFieldMetadataUniversalIdentifier,
|
||||
)?.type ?? referencedField.type)
|
||||
: referencedField.type;
|
||||
|
||||
if (!(effectiveFieldType in FILTER_OPERANDS_MAP)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const allowedOperands = getFilterOperandsForFilterableFieldType({
|
||||
filterType: effectiveFieldType as FilterableAndTSVectorFieldType,
|
||||
subFieldName: filter.subFieldName,
|
||||
});
|
||||
|
||||
if (!allowedOperands.includes(filter.operand)) {
|
||||
errors.push(
|
||||
`Operand "${filter.operand}" is not supported on field "${referencedField.name}" (type "${effectiveFieldType}"). Supported operands: ${allowedOperands.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { BackfillRelationJoinColumnIndexesCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100000000-backfill-relation-join-column-indexes.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ApplicationModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceIteratorModule,
|
||||
WorkspaceMigrationModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
],
|
||||
providers: [BackfillRelationJoinColumnIndexesCommand],
|
||||
})
|
||||
export class V2_8_UpgradeVersionCommandModule {}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
import { Command } from 'nest-commander';
|
||||
import { DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS } from 'twenty-shared/metadata';
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
|
||||
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/get-workspace-schema-context-for-migration.util';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
const POLYMORPHIC_STANDARD_OBJECT_NAMES_SINGULAR: ReadonlySet<string> = new Set(
|
||||
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS,
|
||||
);
|
||||
|
||||
@RegisteredWorkspaceCommand('2.8.0', 1798100000000)
|
||||
@Command({
|
||||
name: 'upgrade:2-8:backfill-relation-join-column-indexes',
|
||||
description:
|
||||
'Backfill missing BTREE indexes on target<X>Id join columns added to polymorphic standard objects (timelineActivity, attachment, noteTarget, taskTarget) when custom objects were created before the auto-index fix. Indexes are created with CONCURRENTLY so writes are not blocked.',
|
||||
})
|
||||
export class BackfillRelationJoinColumnIndexesCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
dataSource,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
if (!dataSource) {
|
||||
this.logger.log(`No data source for workspace ${workspaceId}, skipping`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps, flatIndexMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatIndexMaps',
|
||||
]);
|
||||
|
||||
const polymorphicStandardObjectIds = new Set(
|
||||
Object.values(flatObjectMetadataMaps.byUniversalIdentifier)
|
||||
.filter(isDefined)
|
||||
.filter((flatObject) =>
|
||||
POLYMORPHIC_STANDARD_OBJECT_NAMES_SINGULAR.has(
|
||||
flatObject.nameSingular,
|
||||
),
|
||||
)
|
||||
.map((flatObject) => flatObject.id),
|
||||
);
|
||||
|
||||
if (polymorphicStandardObjectIds.size === 0) {
|
||||
this.logger.log(
|
||||
`No polymorphic standard objects found for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const indexedFieldIds = new Set<string>();
|
||||
|
||||
for (const flatIndex of Object.values(flatIndexMaps.byUniversalIdentifier)) {
|
||||
if (!isDefined(flatIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const indexField of flatIndex.flatIndexFieldMetadatas) {
|
||||
indexedFieldIds.add(indexField.fieldMetadataId);
|
||||
}
|
||||
}
|
||||
|
||||
const fieldsNeedingIndex = Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((flatField) =>
|
||||
polymorphicStandardObjectIds.has(flatField.objectMetadataId),
|
||||
)
|
||||
.filter(
|
||||
(flatField) =>
|
||||
flatField.settings?.relationType === RelationType.MANY_TO_ONE,
|
||||
)
|
||||
.filter((flatField) => !indexedFieldIds.has(flatField.id));
|
||||
|
||||
if (fieldsNeedingIndex.length === 0) {
|
||||
this.logger.log(
|
||||
`No missing relation join column indexes for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const flatIndexBuildPlans = fieldsNeedingIndex.map((flatField) => {
|
||||
const flatObjectMetadata =
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow<FlatObjectMetadata>({
|
||||
flatEntityId: flatField.objectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const universalFlatIndexMetadata = generateIndexForFlatFieldMetadata({
|
||||
flatFieldMetadata: flatField,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatField.name,
|
||||
});
|
||||
|
||||
return {
|
||||
flatObjectMetadata,
|
||||
universalFlatIndexMetadata,
|
||||
joinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Found ${flatIndexBuildPlans.length} missing relation join column index(es) for workspace ${workspaceId}: ${flatIndexBuildPlans.map(({ universalFlatIndexMetadata }) => universalFlatIndexMetadata.name).join(', ')}`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = dataSource.createQueryRunner();
|
||||
let isQueryRunnerConnected = false;
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
isQueryRunnerConnected = true;
|
||||
|
||||
for (const {
|
||||
flatObjectMetadata,
|
||||
universalFlatIndexMetadata,
|
||||
joinColumnName,
|
||||
} of flatIndexBuildPlans) {
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId,
|
||||
objectMetadata: flatObjectMetadata,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.indexManager.createIndex({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
index: {
|
||||
name: universalFlatIndexMetadata.name,
|
||||
columns: [joinColumnName],
|
||||
isUnique: universalFlatIndexMetadata.isUnique,
|
||||
type: universalFlatIndexMetadata.indexType,
|
||||
where: universalFlatIndexMetadata.indexWhereClause ?? undefined,
|
||||
},
|
||||
concurrently: true,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Created index ${universalFlatIndexMetadata.name} on workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (isQueryRunnerConnected) {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
index: {
|
||||
flatEntityToCreate: flatIndexBuildPlans.map(
|
||||
({ universalFlatIndexMetadata }) => universalFlatIndexMetadata,
|
||||
),
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to persist relation join column index metadata:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Failed to persist relation join column index metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled ${flatIndexBuildPlans.length} relation join column index(es) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -10,6 +10,7 @@ import { V2_3_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
|
||||
import { V2_4_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-4/2-4-upgrade-version-command.module';
|
||||
import { V2_5_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-5/2-5-upgrade-version-command.module';
|
||||
import { V2_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-7/2-7-upgrade-version-command.module';
|
||||
import { V2_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -23,6 +24,7 @@ import { V2_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
|
||||
V2_4_UpgradeVersionCommandModule,
|
||||
V2_5_UpgradeVersionCommandModule,
|
||||
V2_7_UpgradeVersionCommandModule,
|
||||
V2_8_UpgradeVersionCommandModule,
|
||||
],
|
||||
})
|
||||
export class WorkspaceCommandProviderModule {}
|
||||
|
||||
-1
@@ -38,7 +38,6 @@ export class EmailVerificationExceptionFilter implements ExceptionFilter {
|
||||
userFriendlyMessage: msg`Email verification not required.`,
|
||||
});
|
||||
case EmailVerificationExceptionCode.INVALID_EMAIL:
|
||||
case EmailVerificationExceptionCode.USER_NOT_FOUND:
|
||||
throw new UserInputError(exception);
|
||||
default: {
|
||||
assertUnreachable(exception.code);
|
||||
|
||||
-3
@@ -13,7 +13,6 @@ export enum EmailVerificationExceptionCode {
|
||||
EMAIL_ALREADY_VERIFIED = 'EMAIL_ALREADY_VERIFIED',
|
||||
INVALID_EMAIL = 'INVALID_EMAIL',
|
||||
RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
|
||||
USER_NOT_FOUND = 'USER_NOT_FOUND',
|
||||
}
|
||||
|
||||
const getEmailVerificationExceptionUserFriendlyMessage = (
|
||||
@@ -34,8 +33,6 @@ const getEmailVerificationExceptionUserFriendlyMessage = (
|
||||
return msg`Invalid email address.`;
|
||||
case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED:
|
||||
return msg`Too many requests. Please try again later.`;
|
||||
case EmailVerificationExceptionCode.USER_NOT_FOUND:
|
||||
return msg`No user found for this email.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
|
||||
+2
-7
@@ -8,7 +8,7 @@ import ms from 'ms';
|
||||
import { SendEmailVerificationLinkEmail } from 'twenty-emails';
|
||||
import { type APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
@@ -138,12 +138,7 @@ export class EmailVerificationService {
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(user)) {
|
||||
throw new EmailVerificationException(
|
||||
'User not found',
|
||||
EmailVerificationExceptionCode.USER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
assertIsDefinedOrThrow(user);
|
||||
|
||||
if (user.isEmailVerified) {
|
||||
throw new EmailVerificationException(
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Korrekte rigting word vereis"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Lêers"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Die Files-veld word nie vir unieke velde ondersteun nie."
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Slegs veldbeperkings word toegelaat."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "الاتجاه الصحيح مطلوب"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "ملفات"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "حقل الملفات غير مدعوم للحقول الفريدة"
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "يُسمح فقط بقيود الحقول."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Cal una direcció vàlida"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Arxius"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "El camp Files no és compatible com a camp únic."
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Només es permeten restriccions de camp."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Je vyžadován správný směr řazení"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Soubory"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Pole Files není podporováno pro unikátní pole."
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Jsou povolena pouze omezení polí."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Korrekt retning er påkrævet"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Filer"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Files-feltet understøttes ikke for unikke felter"
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Kun feltbegrænsninger er tilladt."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Eine gültige Richtung ist erforderlich."
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Dateien"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Das Dateien-Feld wird für eindeutige Felder nicht unterstützt."
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Es sind nur Feldbeschränkungen zulässig."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Απαιτείται ορθή κατεύθυνση"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Αρχεία"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Το πεδίο Files δεν υποστηρίζεται για μοναδικά πεδία"
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Επιτρέπονται μόνο περιορισμοί πεδίων."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1638,6 +1638,11 @@ msgstr "Contradicting field permissions have been detected on a relation field."
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Correct direction is required"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr "Could not create the index because it must run outside a database transaction."
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2779,6 +2784,11 @@ msgstr "Files"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Files field is not supported for unique fields"
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr "Filter operand is not supported for this field type"
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5244,6 +5254,12 @@ msgstr "Only error"
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Only field restrictions are allowed."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Se requiere una dirección válida"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Archivos"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "El campo Files no es compatible con campos únicos"
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Solo se permiten restricciones de campo."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Kelvollinen suunta vaaditaan"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Tiedostot"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Files-kenttää ei tueta yksilöllisille kentille."
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Vain kenttärajoitukset ovat sallittuja."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
@@ -1643,6 +1643,11 @@ msgstr ""
|
||||
msgid "Correct direction is required"
|
||||
msgstr "Une direction correcte est requise"
|
||||
|
||||
#. js-lingui-id: s+lEnL
|
||||
#: src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts
|
||||
msgid "Could not create the index because it must run outside a database transaction."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Fep47m
|
||||
#: src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util.ts
|
||||
msgid "Could not find {targetMetadataName} for given {foreignKey}"
|
||||
@@ -2784,6 +2789,11 @@ msgstr "Fichiers"
|
||||
msgid "Files field is not supported for unique fields"
|
||||
msgstr "Le champ Files n'est pas pris en charge pour les champs uniques"
|
||||
|
||||
#. js-lingui-id: MkIaen
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Filter operand is not supported for this field type"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4GaFCQ
|
||||
#: src/engine/workspace-manager/workspace-migration/interceptors/utils/__tests__/build-metadata-validation-error-payload.util.spec.ts
|
||||
msgid "First friendly message"
|
||||
@@ -5249,6 +5259,12 @@ msgstr ""
|
||||
msgid "Only field restrictions are allowed."
|
||||
msgstr "Seules les restrictions de champ sont autorisées."
|
||||
|
||||
#. js-lingui-id: vX5t8O
|
||||
#. placeholder {0}: allowedOperands.join(', ')
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-filter-validator.service.ts
|
||||
msgid "Operand \"{operand}\" is not supported on field type \"{effectiveFieldType}\". Supported operands: {0}."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 4MyDFl
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-flat-object-metadata.util.ts
|
||||
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user