snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
---
title: Расширение объектов
description: Добавляйте поля к стандартным объектам Twenty (Person, Company, …) или к объектам из других приложений с помощью `defineField`.
icon: wand-magic-sparkles
---
Используйте `defineField()` для добавления поля к объекту, которым вы не владеете — стандартному объекту Twenty, такому как Person или Company, или объекту, поставляемому другим установленным приложением. В отличие от встроенных полей, объявленных внутри [`defineObject`](/l/ru/developers/extend/apps/data/objects), отдельные поля требуют `objectUniversalIdentifier`, чтобы указать, какой объект они расширяют.
```ts src/fields/company-loyalty-tier.field.ts
import { defineField, FieldType } from 'twenty-sdk/define';
export default defineField({
universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890',
objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object
name: 'loyaltyTier',
type: FieldType.SELECT,
label: 'Loyalty Tier',
icon: 'IconStar',
options: [
{ value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' },
{ value: 'SILVER', label: 'Silver', position: 1, color: 'gray' },
{ value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' },
],
});
```
## Основные моменты
* `objectUniversalIdentifier` определяет целевой объект. Для стандартных объектов Twenty импортируйте константу из `twenty-sdk`:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier
// …
```
* При определении полей **непосредственно внутри `defineObject()`** вам **не** нужен `objectUniversalIdentifier` — он наследуется от родительского объекта.
* `defineField()` — единственный способ добавить поля к объектам, которые вы не создавали с помощью `defineObject()`.
* Расположение файла зависит от вас. Принятое соглашение — `src/fields/\<name>.field.ts`, но SDK обнаруживает поля в любом месте внутри `src/`.
* `writability` также может задаваться для каждого поля: `MetadataWritability.APPLICATION` ограничивает запись логическими функциями только вашего приложения, `MetadataWritability.SYSTEM` зарезервирован для данных, управляемых платформой. Изменяемость поля может быть только более строгой, чем уровень его объекта, и чтение при этом не затрагивается. Смотрите раздел [Objects](/l/ru/developers/extend/apps/data/objects) для полного описания семантики.
* Чтобы добавить вкладку в стандартную компоновку страницы (например, на страницу сведений о задаче или компании), используйте [`definePageLayoutTab`](/l/ru/developers/extend/apps/layout/page-layouts#definepagelayouttab) с `STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS` из `twenty-sdk/define`.
## Добавление связи к существующему объекту
Чтобы добавить поле связи (например, связать ваш пользовательский объект со стандартным `Person`), используйте `defineField()` с `FieldType.RELATION`. Шаблон тот же, что и для встроенных связей, но с явным указанием `objectUniversalIdentifier`. Смотрите раздел [Relations](/l/ru/developers/extend/apps/data/relations) для двунаправленного шаблона.

View File

@@ -0,0 +1,143 @@
---
title: Объекты
description: Объявляйте новые типы записей — пользовательские таблицы с собственными полями — с помощью defineObject.
icon: table
---
Пользовательские **объекты** — это новые типы записей, которые ваше приложение добавляет в рабочее пространство — открытка, счёт-фактура, подписка, что‑то специфичное для вашей предметной области. Каждый объект объявляет свою схему (поля, связи, значения по умолчанию) и стабильный универсальный идентификатор, который сохраняется между синхронизациями и развёртываниями.
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
## Основные моменты
* `universalIdentifier` должен быть уникальным и стабильным между развёртываниями.
* Каждому полю требуются `name`, `type`, `label` и собственный стабильный `universalIdentifier`.
* Массив `fields` необязателен — вы можете определять объекты без пользовательских полей.
* `openRecordIn` определяет, где открываются записи этого объекта при щелчке: `ObjectOpenRecordIn.USER_CHOICE` (по умолчанию, в соответствии с индивидуальными настройками каждого участника рабочего пространства в Settings → Experience), `ObjectOpenRecordIn.SIDE_PANEL` или `ObjectOpenRecordIn.RECORD_PAGE`. Закрепите его на `RECORD_PAGE` для записей, которым для полноценного использования нужна целая страница (как у рабочих процессов и дашбордов), или на `SIDE_PANEL` для записей, которые уместны только в виде быстрой боковой панели (как события календаря).
* `writability` определяет, кто в принципе может выполнять операции записи с записями объекта — до того, как начнут действовать разрешения ролей: `MetadataWritability.OPEN` (значение по умолчанию — решают роли рабочего пространства), `MetadataWritability.APPLICATION` (создавать, обновлять и удалять записи могут только собственные логические функции вашего приложения; используйте это для «конфигурационных» объектов, чьи записи задают поведение, чтобы участники рабочего пространства с широким доступом к записям не могли редактировать их через API) или `MetadataWritability.SYSTEM` (зарезервировано для данных, управляемых платформой). Операции чтения не затрагиваются — это ограничение применяется на стороне сервера, в отличие от `isUIEditable`, который лишь скрывает элементы интерфейса. Оно также существует на уровне отдельного поля, где может быть только более строгим, чем на уровне объекта.
* Встроенным полям, определённым здесь, **не** нужен `objectUniversalIdentifier` — он наследуется от родительского объекта. Используйте [`defineField()`](/l/ru/developers/extend/apps/data/extending-objects), чтобы добавить поля к объектам, которые вам не принадлежат.
* Вы можете сгенерировать заготовки новых объектов с помощью `yarn twenty dev:add object`, который проведёт вас через настройку названий, полей и связей. См. [Architecture → Scaffolding entities](/l/ru/developers/extend/apps/getting-started/scaffolding).
<Note>
**Базовые поля добавляются автоматически.** Когда вы определяете пользовательский объект, Twenty создаёт для вас стандартные поля, такие как `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` и `deletedAt`. Вам не нужно объявлять их в массиве `fields` — только ваши пользовательские поля. Вы можете переопределить базовое поле, объявив поле с тем же именем, но это редко бывает хорошей идеей.
</Note>
## Типы полей
Полный набор значений `FieldType`, экспортируемых из `twenty-sdk/define`:
| Категория | Типы |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Текст | `TEXT`, `RICH_TEXT`, `ARRAY` (строк), `RAW_JSON` |
| Числовые | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (произвольная точность), `RATING`, `POSITION` |
| Даты | `DATE`, `DATE_TIME` |
| Выбор | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Составные | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Идентификаторы и связи | `UUID`, `RELATION`, `MORPH_RELATION` (см. [Relations](/l/ru/developers/extend/apps/data/relations)) |
| Система | `TS_VECTOR` (вектор полнотекстового поиска, управляется сервером) |
Составные типы хранят несколько подполей (например, `FULL_NAME` = имя + фамилия; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` и `MULTI_SELECT` требуют массива `options`, как в примере выше.
## Значения по умолчанию
Строковые значения по умолчанию должны быть заключены в одинарные кавычки **внутри** строки — `defaultValue: "'Draft'"`, а не `defaultValue: "Draft"`. Именно поэтому поле `status` выше использует `` `'${PostCardStatus.DRAFT}'` ``.
Незаключённые в кавычки строки зарезервированы для вычисляемых значений по умолчанию, которые рассчитываются при создании записи:
* `'uuid'` — генерирует UUID (для полей `UUID`)
* `'now'` — текущая временная метка (для полей `DATE_TIME`)
Та же конвенция применяется к строковым подполям составных значений по умолчанию (например, `{ source: "'MANUAL'" }` в поле `ACTOR`) и к значениям `SELECT`/`MULTI_SELECT`. Строковый литерал по умолчанию, оставленный без кавычек, вызывает предупреждение при сборке приложения.
## Допустимость значения NULL
`isNullable` определяет, допускает ли поле значение `NULL`. По умолчанию установлено значение `true` — опускайте этот параметр для необязательных полей. Установите `isNullable: false`, чтобы сделать поле обязательным на уровне базы данных.
Изменения `isNullable` применяются при каждой синхронизации, включая синхронизации, которые обновляют существующее поле, — таким образом, вы можете изменить допустимость значения NULL для поля, отредактировав манифест и повторно выполнив синхронизацию.
<Note>
**Чтобы сделать существующее поле не допускающим значение NULL, требуется значение по умолчанию.** Когда вы изменяете поле на `isNullable: false`, вы также должны предоставить значение `defaultValue`, отличное от `NULL`. Значение по умолчанию заполняет все существующие строки с `NULL` перед применением ограничения `NOT NULL`; без него синхронизация завершится с ошибкой `Default value cannot be null for non-nullable fields`. Поля связей и поля `TS_VECTOR` всегда допускают значение NULL, поэтому параметр `isNullable` на них не влияет.
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## Что дальше
* **Свяжите этот объект с другими** — см. [Relations](/l/ru/developers/extend/apps/data/relations) для двунаправленного шаблона связей.
* **Добавляйте поля к объектам из других приложений** — см. [Extending Objects](/l/ru/developers/extend/apps/data/extending-objects) о `defineField()`.
* **Отображать этот объект в интерфейсе** — см. [пункты меню навигации](/l/ru/developers/extend/apps/layout/navigation-menu-items), чтобы добавить пункт бокового меню; см. [представления](/l/ru/developers/extend/apps/layout/views), чтобы добавить пользовательские настройки списков.

View File

@@ -0,0 +1,101 @@
---
title: Обзор
description: Определяйте структуру данных, которые ваше приложение добавляет в рабочее пространство — объекты, поля и связи.
icon: database
---
**Уровень данных** приложения Twenty — это данные, которые ваше приложение *добавляет* в рабочее пространство: новые типы записей, которые оно объявляет, столбцы, которые оно добавляет к существующим объектам, и то, как эти записи связываются друг с другом.
```text
┌──────────────────────────────────────────────────┐
│ Object — a record type, e.g. PostCard │
│ ├─ Field (name, type, label) │
│ ├─ Field │
│ └─ Relation (link to another object) │
└──────────────────────────────────────────────────┘
│
├── lives in your app, OR
│
▼
┌──────────────────────────────────────────────────┐
│ Standard / other apps' objects │
│ └─ Field added by your app via defineField │
└──────────────────────────────────────────────────┘
```
## В этом разделе
<CardGroup cols={2}>
<Card title="Объекты" icon="table" href="/l/ru/developers/extend/apps/data/objects">
`defineObject` — объявляйте новые типы записей с их собственными полями.
</Card>
<Card title="Расширение объектов" icon="wand-magic-sparkles" href="/l/ru/developers/extend/apps/data/extending-objects">
`defineField` — добавляйте поля к стандартным объектам или объектам других приложений.
</Card>
<Card title="Связи" icon="diagram-project" href="/l/ru/developers/extend/apps/data/relations">
Двусторонние связи `MANY_TO_ONE` / `ONE_TO_MANY` между объектами.
</Card>
<Card title="Типы действий на временной шкале" icon="clock-rotate-left" href="/l/ru/developers/extend/apps/data/timeline-activity-types">
Определяйте автоматические события аудита и явные события приложения для временных шкал записей.
</Card>
</CardGroup>
## Сущности вкратце
| Сущность | Назначение | Определяется с помощью |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| **Объект** | Новый пользовательский тип записей (например, PostCard, Invoice) с собственными полями | `defineObject()` |
| **Поле** | Столбец в объекте. Отдельные поля могут расширять объекты, которые вы не создавали (например, добавьте `loyaltyTier` к объекту Company) | `defineField()` |
| **Связь** | Двусторонняя связь между двумя объектами — обе стороны объявлены как поля | `defineField()` с `FieldType.RELATION` |
| **Индекс** | Индекс базы данных для ускорения повторяющегося запроса к одному из ваших объектов | `defineIndex()` |
| **Тип действия на временной шкале** | Автоматическое событие аудита или явное событие домена, отображаемое на временных шкалах записей | `defineTimelineActivityType()` |
SDK обнаруживает их с помощью анализа AST во время сборки, поэтому организация файлов остается на ваше усмотрение — по соглашению используются `src/objects/`, `src/fields/`, `src/indexes/` и `src/timeline-activity-types/`. Стабильные UUID `universalIdentifier` связывают все воедино между развертываниями.
## Индексы (необязательно)
Приложения могут поставлять индексы вместе со своими объектами, чтобы повторяющиеся запросы выполнялись быстро. Наиболее распространенный случай — столбец статуса или внешнего ключа, к которому вы часто обращаетесь при чтении.
```ts src/indexes/post-card-status.index.ts
import { defineIndex } from 'twenty-sdk/define';
import {
POST_CARD_UNIVERSAL_IDENTIFIER,
STATUS_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card.object';
export default defineIndex({
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff0',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff1',
fieldUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
},
],
});
```
### Уникальные индексы
`defineIndex` принимает `isUnique: true` как для уникальности по одному столбцу, так и по нескольким столбцам. Это рекомендуемый примитив — `defineField({ isUnique: true })` устарел и будет удален в одном из будущих релизов.
```ts
defineIndex({
universalIdentifier: '…',
objectUniversalIdentifier: PERSON_UNIVERSAL_IDENTIFIER,
isUnique: true,
fields: [{ universalIdentifier: '…', fieldUniversalIdentifier: EMAIL_FIELD_UNIVERSAL_IDENTIFIER }],
});
```
### Другие ограничения
* Частичные предложения `WHERE` остаются под контролем администратора — приложения не могут объявлять их.
* Для каждого объекта допускается не более 10 пользовательских индексов (индексы самого фреймворка не учитываются).
Упорядочьте массив `fields` в том порядке, в котором Postgres должен его использовать — сначала самый левый столбец, как в телефонной книге. Индексы не бесплатны: при каждой записи в таблицу они обновляются. Добавляйте индекс только тогда, когда у вас есть запрос, которому он действительно нужен.
<Note>
Ищете **Application Config** или **Roles & Permissions**? Они описывают само приложение, а не данные, которые оно добавляет, — их можно найти в разделе [Config](/l/ru/developers/extend/apps/config/overview). Ищете **Connections** (Linear, GitHub, Slack OAuth)? Они существуют для вызова *из* логических функций и находятся в разделе [Logic](/l/ru/developers/extend/apps/logic/connections).
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Связи
description: Связывайте объекты между собой двунаправленными связями MANY_TO_ONE / ONE_TO_MANY.
icon: diagram-project
---
Отношения связывают два объекта между собой. В Twenty отношения всегда **двунаправленные** — у каждого отношения есть две стороны, и каждая сторона объявляется как поле, ссылающееся на другую.
| Тип отношения | Описание | Есть внешний ключ? |
| ------------- | --------------------------------------------------------------------- | ---------------------- |
| `MANY_TO_ONE` | Многие записи этого объекта указывают на одну запись целевого объекта | Да (`joinColumnName`) |
| `ONE_TO_MANY` | Одна запись этого объекта имеет много записей целевого объекта | Нет (обратная сторона) |
## Как работают отношения
Каждое отношение требует **двух полей**, которые ссылаются друг на друга:
1. Сторона **MANY_TO_ONE** — находится в объекте, который содержит внешний ключ.
2. Сторона **ONE_TO_MANY** — находится в объекте, которому принадлежит коллекция.
Оба поля используют `FieldType.RELATION` и ссылаются друг на друга через `relationTargetFieldMetadataUniversalIdentifier`.
## Пример: Почтовая открытка имеет много получателей
`PostCard` может быть отправлен множеству записей `PostCardRecipient`. Каждый получатель относится ровно к одной открытке.
**Шаг 1: Определите сторону ONE_TO_MANY на PostCard** (сторона "one"):
```ts src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111';
// Import from the other side
import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field';
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
icon: 'IconUsers',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
```
**Шаг 2: Определите сторону MANY_TO_ONE на PostCardRecipient** (сторона "many" — содержит внешний ключ):
```ts src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222';
// Import from the other side
import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field';
export default defineField({
universalIdentifier: POST_CARD_FIELD_ID,
objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
icon: 'IconMail',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
});
```
<Note>
**Циклические импорты:** оба поля отношений ссылаются на `universalIdentifier` друг друга. Чтобы избежать проблем с циклическими импортами, экспортируйте идентификаторы полей как именованные константы из каждого файла и импортируйте их в другом. Система сборки разрешает это на этапе компиляции.
</Note>
## Связи через промежуточный объект
Промежуточный объект связывает одну исходную запись с одной или несколькими целевыми записями. Например, `PostCardRecipient` может содержать связь `postCard` и связь `recipient`. В поле `ONE_TO_MANY` исходного объекта задайте для `junctionTargetFieldUniversalIdentifier` поле связи в промежуточном объекте, указывающее на цель:
```ts
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
relationTargetObjectMetadataUniversalIdentifier:
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
junctionTargetFieldUniversalIdentifier: RECIPIENT_FIELD_ID,
},
});
```
`RECIPIENT_FIELD_ID` должен идентифицировать связь `MANY_TO_ONE` или морфную связь в промежуточном объекте. Это сообщает Twenty, какая связанная запись является целевой, когда функция, например [распределение действий временной шкалы](/l/ru/developers/extend/apps/data/timeline-activity-types#automatic-and-explicit-events), проходит через промежуточный объект.
## Связывание со стандартными объектами
Чтобы создать отношение со встроенным объектом Twenty (Person, Company и т. д.), используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
```ts src/fields/person-on-self-hosting-user.field.ts
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
export default defineField({
universalIdentifier: PERSON_FIELD_ID,
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
description: 'Person matching with the self hosting user',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'personId',
},
});
```
## Свойства поля отношения
| Свойство | Обязательно | Описание |
| ---------------------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `type` | Да | Должно быть `FieldType.RELATION` |
| `relationTargetObjectMetadataUniversalIdentifier` | Да | `universalIdentifier` целевого объекта |
| `relationTargetFieldMetadataUniversalIdentifier` | Да | `universalIdentifier` соответствующего поля на целевом объекте |
| `universalSettings.relationType` | Да | `RelationType.MANY_TO_ONE` или `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | Только для MANY_TO_ONE | Что происходит при удалении связанной записи: `CASCADE`, `SET_NULL`, `RESTRICT` или `NO_ACTION` |
| `universalSettings.joinColumnName` | Только для MANY_TO_ONE | Имя столбца базы данных для внешнего ключа (например, `postCardId`) |
| `universalSettings.junctionTargetFieldUniversalIdentifier` | Только для промежуточного объекта ONE_TO_MANY | Поле связи в промежуточном объекте, указывающее на цель распределения |
## Встроенные поля связей
Вы также можете объявить связь напрямую внутри [`defineObject`](/l/ru/developers/extend/apps/data/objects). При встроенном объявлении опустите `objectUniversalIdentifier` — он наследуется от родительского объекта:
```ts
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCardRecipient',
// ...
fields: [
{
universalIdentifier: POST_CARD_FIELD_ID,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
},
// … other fields
],
});
```

View File

@@ -0,0 +1,211 @@
---
title: Обращение к системным метаданным
description: Определяйте детерминированные универсальные идентификаторы метаданных, которые Twenty автоматически подготавливает для каждого объекта, чтобы ваше приложение могло ссылаться на них без жёсткого кодирования.
icon: gears
---
Каждый объект в Twenty содержит **системные метаданные**, которые вы сами никогда не объявляете, например набор полей и основное представление списка с его столбцами. Сервер создаёт всё это при подготовке объекта, и этот набор растёт по мере развития Twenty.
Поскольку вы это не объявляете, у вас нет константы `universalIdentifier`, которую можно импортировать. Вместо этого сервер **выводит** каждый идентификатор детерминированным образом, а `twenty-sdk` предоставляет тот же механизм вывода, чтобы ваш манифест мог получить точное значение, которое использует сервер.
## Системные поля
Скалярные поля, присутствующие в каждом объекте, ни одно из которых вы не объявляете с помощью [`defineField()`](/l/ru/developers/extend/apps/data/extending-objects):
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
Тогда как сослаться на `createdAt` как на столбец в [представлении](/l/ru/developers/extend/apps/layout/views)?
### Проблема
Начиная с Twenty 2.19, универсальный идентификатор системного поля **детерминированно выводится** сервером из трех входных данных: универсального идентификатора приложения, универсального идентификатора объекта и имени поля. Придумать id и захардкодить его не получится: он не соответствует ничему на сервере, и синхронизация отклоняет висящую ссылку:
```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
```
### Решение
<Note>
`getFieldUniversalIdentifier` доступен, начиная с `twenty-sdk` версии 2.21.
</Note>
Используйте `getFieldUniversalIdentifier`, чтобы получить в точности то же значение, которое использует сервер. Он принимает три входных параметра и возвращает универсальный идентификатор поля:
```ts
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
const createdAtFieldId = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
});
```
* `applicationUniversalIdentifier` — это идентификатор вашего приложения, тот, который вы передаете в [`defineApplication()`](/l/ru/developers/extend/apps/config/application).
* `objectUniversalIdentifier` — это идентификатор объекта, к которому принадлежит поле.
* `name` — это имя системного поля, одно из значений, перечисленных выше.
### Пример: столбец createdAt в представлении
Типичный случай — добавление столбца `createdAt` в представление одного из ваших пользовательских объектов. Разрешите id поля и ссылайтесь на него так же, как на любой другой `fieldMetadataUniversalIdentifier`:
```ts src/views/example-view.ts
import {
defineView,
getFieldUniversalIdentifier,
} from 'twenty-sdk/define';
const APPLICATION_UNIVERSAL_IDENTIFIER =
'0b04e15c-27b2-4741-9046-b32e07469072';
const MY_OBJECT_UNIVERSAL_IDENTIFIER =
'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
export default defineView({
universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
name: 'All records',
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
}),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
Тот же разрешенный id работает везде, где ожидается `fieldMetadataUniversalIdentifier`: поля представления, фильтры, сортировки, группировки и виджеты макета страницы.
<Note>
Разрешайте id, не хардкодьте его. Поскольку сервер выводит значение из
id приложения, id объекта и имени поля, вызов
`getFieldUniversalIdentifier` сохраняет вашу ссылку корректной, даже если эти
входные данные изменятся, и предотвращает расхождение, если способ вывода когда-нибудь изменится.
</Note>
### Системные поля связей
<Note>
`getSystemRelationFieldUniversalIdentifier` доступен в `twenty-sdk`, начиная с версии 2.23, и требует сервер Twenty версии 2.23 или новее.
</Note>
Помимо перечисленных выше скалярных системных полей, сервер также подготавливает четыре **системных поля связей** для каждого объекта: `timelineActivities`, `attachments`, `noteTargets` и `taskTargets`, каждое из которых указывает на соответствующий стандартный объект связи.
Таким образом, эти поля не разрешаются с помощью `getFieldUniversalIdentifier`: их идентификатор выводится **безотносительно имени** из объекта, содержащего поле, и объекта, на который поле указывает. Таким образом, переименование объекта никогда не изменяет идентификаторы его полей связей.
Используйте `getSystemRelationFieldUniversalIdentifier`, чтобы разрешить их:
```ts
import {
getSystemRelationFieldUniversalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
// rocket.attachments — the relation field hosted on your custom object
const rocketAttachmentsFieldId = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
});
```
* `objectUniversalIdentifier` — это объект, **содержащий** поле.
* `relationTargetObjectUniversalIdentifier` — это объект, на который **указывает** поле.
Направление кодируется порядком аргументов. Чтобы получить обратную сторону (например, `attachment.targetRocket`, morph‑поле, которое сервер создаёт на стандартном объекте связи), поменяйте их местами:
```ts
// attachment.targetRocket — the reverse morph field on Attachment
const attachmentTargetRocketFieldId =
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
relationTargetObjectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
});
```
Как и в случае со скалярными системными полями, разрешённый id работает везде, где ожидается `fieldMetadataUniversalIdentifier`.
## Системные представления
<Note>
`getSystemViewUniversalIdentifier` и `getSystemViewFieldUniversalIdentifier`
доступны в `twenty-sdk`, начиная с версии 2.26, и требуют сервер Twenty
версии 2.26 или новее.
</Note>
Сервер также подготавливает **системное представление** для каждого объекта: основное представление списка (`All {objectLabelPlural}`, с ключом `ViewKey.INDEX` (полученным с помощью `SYSTEM_VIEW_KEYS.INDEX`)), с одним столбцом на каждое отображаемое поле. Как и у системных полей связей, их идентификаторы выводятся **без использования имён**, поэтому переименование объекта или поля никогда их не меняет.
Используйте `getSystemViewUniversalIdentifier`, чтобы получить идентификатор представления:
```ts
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from 'twenty-sdk/define';
const rocketIndexViewId = getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
});
```
* `objectMetadataApplicationUniversalIdentifier` — это приложение, которому принадлежит **объект**, и именно по нему задаётся пространство имён представления.
* `objectUniversalIdentifier` — это объект, который отображается в представлении.
* `viewKey` — это дискриминатор системного представления: `SYSTEM_VIEW_KEYS.INDEX` для основного представления списка, `SYSTEM_VIEW_KEYS.FIELDS_WIDGET` для представления виджета полей на странице записи. Он определяет способ вычисления представления; только `INDEX` также сохраняется в строке представления.
Полученный идентификатор подходит везде, где ожидается `viewUniversalIdentifier`, например для пункта боковой панели [`NavigationMenuItemType.VIEW`](/l/ru/developers/extend/apps/layout/navigation-menu-items). Чтобы просто открыть основной список объекта, предпочитайте `NavigationMenuItemType.OBJECT` с `targetObjectUniversalIdentifier`: здесь не нужен вывод идентификатора.
`getSystemViewFieldUniversalIdentifier` определяет идентификатор одного **столбца** в системном представлении по самому представлению и отображаемому в нём полю:
```ts
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
viewUniversalIdentifier: rocketIndexViewId,
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
});
```
Обратите внимание на первый аргумент: пространство имён столбца задаётся приложением, которому принадлежит **поле, отображаемое в нём**, а не приложением, которому принадлежит представление. Поле, которое ваше приложение добавляет к стандартному объекту, получает свой столбец, выводимый в пространстве имён вашего приложения, на представлении, принадлежащем Twenty.
<Warning>
Системные представления и их столбцы **принадлежат серверу**: разрешайте их идентификаторы, чтобы ссылаться на них, но никогда не объявляйте их сами. `key` в
[`defineView()`](/l/ru/developers/extend/apps/layout/views) устарел и
игнорируется, поэтому представление из манифеста никогда не может заявить ключ `INDEX`, а сервер уже подготавливает столбец для каждого добавленного вами поля, так что объявление собственного
`defineViewField()` для того же поля в системном представлении конфликтует с ним.
</Warning>
## Стандартные объекты Twenty
Для **стандартного** объекта Twenty (Person, Company, Opportunity, …) вам не нужно ничего выводить: идентификаторы как полей, так и представлений — это заранее вычисленные константы, которые вы можете импортировать напрямую.
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier
```
Используйте указанные выше вспомогательные функции, когда объект — это объект, который **ваше приложение** определяет с помощью [`defineObject()`](/l/ru/developers/extend/apps/data/objects), где такой константы не существует.
<Note>
`name` — это **поле по умолчанию**, а не системное поле. У него есть собственный захардкоженный
универсальный идентификатор, и он не разрешается через
`getFieldUniversalIdentifier`. В определяемых вами объектах ссылайтесь на поле
`name` по идентификатору, который вы задали ему в `defineObject()`.
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Типы активности хронологии
description: Определяет автоматические события аудита и события приложения, отображающие время записи.
icon: clock-rotate-left
---
Тип активности временной шкалы определяет стабильный словарь событий, отображаемых на временной шкале. Стандартные объекты и прикладные объекты используют тот же контракт: тип имеет метку и иконку, можно объявить при выдаче, и может при желании отображать один из [фронтальных компонентов](/l/ru/developers/extend/apps/layout/front-components).
<Note>
Виды активностей на временной шкале находятся в бета-версии и скоро появятся в разделе Двадцать 2.34.
API может развиваться, пока мы учимся у разработчиков приложений.
</Note>
Создайте один с помощью лесника:
```bash filename="Terminal"
yarn twenty dev:add timelineActivityType
```
Или определите его напрямую:
```ts filename="src/timeline-activity-types/post-card-created.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7500',
name: 'postCardCreated',
label: 'created a post card',
icon: 'IconMail',
emit: {
on: 'created',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
},
});
```
## Автоматические и явные события
Добавьте `emit`, когда двадцать должен создать этот тип автоматически. `emit.on` поддерживает `created`, `updated`, `deleted`, `restored`, `linked`, и `unlinked`, а `emit.objectUniversalIdentifier` идентифицирует объект источника. Только один эффективный тип может справиться с тем же эмулируемым ключом - `on`, объектом и необязательным - в рабочей области.
Без `emit.through` событие записывается на собственной временной шкале источника. Чтобы пообщаться с соответствующими записями, установите `emit.through. elationFieldUniversalIdentifier` в исходном объекте либо прямое отношение множества к одному или [отношение одного-много соединения](/l/ru/developers/extend/apps/data/relations#junction-relations). Morph отношения с каждым членом группы морфов, так что одно объявление может быть направлено на несколько типов объектов.
Для прямой связи создание или восстановление записи источника производит `linked`, удаляя ее производит `unlinked`, и перенаправление отношения приводит к `unlinked` на предыдущей цели плюс `linked` на новую цель. Другие обновления источников не дают ссылки на события. Это контракт используется приложениями, целью которых является прямая связь.
Для связи между перекрёстками 'universalSettings.junctionTargetFieldUniversalIdentifier' идентифицирует отношение объекта соединения к цели. Создание или удаление строки перекрёстка производит `linked` или `unlinked`. Переадресация на обе стороны строки перекрёсток также производит ссылку событие; обновления к несвязанным полям не делают.
События `linked` и `unlinked` требуют `emit.through`, потому что их триггер-триггер является изменением настроенной связи или соединения.
Например, это тот же общий контракт, который используется заметками, задачами, сообщениями и событиями в календаре:
```ts filename="src/timeline-activity-types/post-card-linked.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/post-card-recipients-on-post-card.field';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7502',
name: 'postCardLinked',
label: 'received a post card',
icon: 'IconMail',
emit: {
on: 'linked',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
through: {
relationFieldUniversalIdentifier:
POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER,
},
},
});
```
По умолчанию для каждого обновления источника выдается параметр «updated» через событие. Установите `emit.through.triggerFieldUniversalIdentifiers`, когда изменяются только выбранные исходные поля должны отображаться в целевые сроки.
Omit `emit` для явного события домена, которое создаёт ваша логическая функция. Это позволяет избежать автоматического аудита и явной строки для одной и той же операции.
```ts filename="src/timeline-activity-types/post-card-sent.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
});
```
Создать явные события с помощью [`createTimelineActivity()`](/l/ru/developers/extend/apps/logic/logic-functions#create-a-timeline-activity). Код приложения использует стабильные универсальные идентификаторы; двадцать разрешает идентификаторы метаданных, специфичные для установки.
## Пользовательский рендеринг
Без переднего компонента двадцать передает обычный общий ряд из метаданных типа, значка и связанных объектов. Это работает для стандартных и пользовательских объектов без объектно-специфичного визуализатора.
Для пользовательских деталей установите `frontComponentUniversalIdentifier` на передний компонент, принадлежащий тому же приложению:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Натуральная строка остается свернутой презентацией. Двадцать монтируют передний компонент только после того, как пользователь расширяет ряд, избегая песочницы и рабочих для каждого видимого события. Внутри компонента вызовите `useTimelineActivityId()` из `twenty-sdk/front-component` для чтения ID строки и получения любых данных, необходимых для презентации. Возвращает `null`, когда компонент отображается за пределами ленты времени.
## Переопределение презентации другого приложения
Приложение имеет автоматические контракты на временной шкале для собственных объектов. Чтобы настроить событие на объекте, принадлежащем другому приложению, явно объявить существующий тип заменой `reTimelineActivityTypeUniversalIdentifier`:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7503',
name: 'companyCreatedWithDeploymentContext',
label: 'was created from a deployment',
emit: {
on: 'created',
objectUniversalIdentifier: COMPANY_UNIVERSAL_IDENTIFIER,
},
replacesTimelineActivityTypeUniversalIdentifier:
RECORD_CREATED_TIMELINE_ACTIVITY_TYPE_UNIVERSAL_IDENTIFIER,
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Переопределение должно включать `emit`, потому что оно заменяет существующую ячейку автоматического излучения вместо явного типа. Указанный тип должен принадлежать приложению целевого объекта и описывать одно и то же действие и маршрут. Удаление переопределения приложения возвращает базовый тип; удаление базового типа отключает переопределение.
## Рабочая область переопределяет и отключает звук
Администраторы рабочего пространства могут изменять презентацию типа или отключать автоматические и явные события без форсирования приложения. Используйте мутацию `updateTimelineActivityType` метаданных API с идентификатором определенного типа установки:
```graphql
mutation CustomizeTimelineActivityType(
$id: UUID!
$label: String
$icon: String
$isActive: Boolean
) {
updateTimelineActivityType(
input: { id: $id, label: $label, icon: $icon, isActive: $isActive }
) {
id
label
icon
isActive
}
}
```
`label` и `icon` хранятся как переопределение рабочего пространства, поэтому последующие обновления приложения не перезаписывают выбор администратора. Установите `isActive` в `false`, чтобы остановить автоматическое излучение и отклонить новые явные события этого типа. Существующие строки остаются видимыми.
Восстановить настройки по умолчанию, включая активное состояние, например:
```graphql
mutation ResetTimelineActivityType($id: UUID!) {
resetTimelineActivityType(id: $id) {
id
label
icon
isActive
}
}
```
## Поля конфигурации
| Поле | Обязательно | Описание |
| -------------------------------------------------- | ------------ | ----------------------------------------------------------------------------- |
| `universalIdentifier` | Да | Стабильный UUID для всех установок и обновлений |
| `name` | Да | Локальное для приложения программное имя; должно быть уникальным в приложении |
| `label` | Да | Текст действий для пользователя, используемый родным рендерером |
| `icon` | Нет | Двадцать иконок, отображаемое рядом с событием |
| `emit` | Нет | Автоматическое заявление о выбросах; опущение только для явных типов |
| `emit.on` | С излучением | Ревизия для записи этого типа |
| `emit.objectUniversalIdentifier` | С излучением | Объект с записями излучающей этот тип |
| `emit.through.relationFieldUniversalIdentifier` | Нет | Прямое или перекрёстое, используемое для вентиляции в соответствующие сроки |
| `emit.through.triggerFieldUniversalIdentifiers` | Нет | Исходные поля, которые могут инициировать «обновление» через событие |
| `frontComponentUniversalIdentifier` | Нет | Собственный фронтальный компонент монтируется при расширении ряда |
| `заменяет TimelineActivityTypeUniversalIdentifier` | Нет | Существующий тип излучения заменён, требует `emit` |
Двадцать снимков семантическая идентичность и прочная презентация, когда событие создается. Исторические строки сохраняют исходное значение действия и объекта. Пока тип установлен, в live отображаются текущая метка перевода, иконка и передние компоненты; после удаления резервный снимок сохраняет строку читаемой. Резолюция использует универсальный идентификатор, поэтому презентация также выживает после переустановки приложения.