mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
i18n - docs translations (#19925)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
parent
15938c1fca
commit
cd73088be6
462 changed files with 23407 additions and 21106 deletions
|
|
@ -802,7 +802,7 @@
|
|||
"language": "ar",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "البدء",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -831,7 +831,7 @@
|
|||
"tab": "دليل المستخدم",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "نظرة عامة",
|
||||
"pages": [
|
||||
"l/ar/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -1004,7 +1004,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Layout",
|
||||
"group": "التخطيط",
|
||||
"icon": "table-columns",
|
||||
"pages": [
|
||||
"l/ar/user-guide/layout/overview",
|
||||
|
|
@ -1013,7 +1013,7 @@
|
|||
"pages": [
|
||||
"l/ar/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "العروض",
|
||||
"pages": [
|
||||
"l/ar/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/ar/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -1027,7 +1027,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "الإرشادات",
|
||||
"pages": [
|
||||
"l/ar/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/ar/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -1132,7 +1132,7 @@
|
|||
"tab": "المطورون",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "نظرة عامة",
|
||||
"pages": [
|
||||
"l/ar/developers/introduction"
|
||||
]
|
||||
|
|
@ -1152,7 +1152,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "API",
|
||||
"group": "واجهة برمجة التطبيقات",
|
||||
"pages": [
|
||||
"l/ar/developers/extend/api",
|
||||
"l/ar/developers/extend/webhooks",
|
||||
|
|
@ -1185,7 +1185,7 @@
|
|||
"language": "cs",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "Začínáme",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -1214,7 +1214,7 @@
|
|||
"tab": "Uživatelská příručka",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Přehled",
|
||||
"pages": [
|
||||
"l/cs/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -1387,7 +1387,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Layout",
|
||||
"group": "Rozvržení",
|
||||
"icon": "table-columns",
|
||||
"pages": [
|
||||
"l/cs/user-guide/layout/overview",
|
||||
|
|
@ -1396,7 +1396,7 @@
|
|||
"pages": [
|
||||
"l/cs/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "Zobrazení",
|
||||
"pages": [
|
||||
"l/cs/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/cs/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -1410,7 +1410,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "Návody",
|
||||
"pages": [
|
||||
"l/cs/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/cs/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -1515,7 +1515,7 @@
|
|||
"tab": "Vývojáři",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Přehled",
|
||||
"pages": [
|
||||
"l/cs/developers/introduction"
|
||||
]
|
||||
|
|
@ -1568,7 +1568,7 @@
|
|||
"language": "de",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "Erste Schritte",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -1597,7 +1597,7 @@
|
|||
"tab": "Benutzerhandbuch",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Übersicht",
|
||||
"pages": [
|
||||
"l/de/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -1779,7 +1779,7 @@
|
|||
"pages": [
|
||||
"l/de/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "Ansichten",
|
||||
"pages": [
|
||||
"l/de/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/de/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -1793,7 +1793,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "Anleitungen",
|
||||
"pages": [
|
||||
"l/de/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/de/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -1898,7 +1898,7 @@
|
|||
"tab": "Entwickler",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Übersicht",
|
||||
"pages": [
|
||||
"l/de/developers/introduction"
|
||||
]
|
||||
|
|
@ -2334,7 +2334,7 @@
|
|||
"language": "it",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "Per iniziare",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -2363,7 +2363,7 @@
|
|||
"tab": "Guida utente",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Panoramica",
|
||||
"pages": [
|
||||
"l/it/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -2536,7 +2536,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Layout",
|
||||
"group": "Disposizione",
|
||||
"icon": "table-columns",
|
||||
"pages": [
|
||||
"l/it/user-guide/layout/overview",
|
||||
|
|
@ -2545,7 +2545,7 @@
|
|||
"pages": [
|
||||
"l/it/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "Viste",
|
||||
"pages": [
|
||||
"l/it/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/it/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -2559,7 +2559,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "Guide pratiche",
|
||||
"pages": [
|
||||
"l/it/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/it/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -2664,7 +2664,7 @@
|
|||
"tab": "Sviluppatori",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Panoramica",
|
||||
"pages": [
|
||||
"l/it/developers/introduction"
|
||||
]
|
||||
|
|
@ -3483,7 +3483,7 @@
|
|||
"language": "pt",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "Primeiros passos",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -3512,7 +3512,7 @@
|
|||
"tab": "User Guide",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Visão Geral",
|
||||
"pages": [
|
||||
"l/pt/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -3694,7 +3694,7 @@
|
|||
"pages": [
|
||||
"l/pt/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "Visualizações",
|
||||
"pages": [
|
||||
"l/pt/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/pt/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -3813,7 +3813,7 @@
|
|||
"tab": "Programadores",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Visão Geral",
|
||||
"pages": [
|
||||
"l/pt/developers/introduction"
|
||||
]
|
||||
|
|
@ -4249,7 +4249,7 @@
|
|||
"language": "ru",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "Начало работы",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -4278,7 +4278,7 @@
|
|||
"tab": "Руководство пользователя",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Обзор",
|
||||
"pages": [
|
||||
"l/ru/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -4451,7 +4451,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Layout",
|
||||
"group": "Макет",
|
||||
"icon": "table-columns",
|
||||
"pages": [
|
||||
"l/ru/user-guide/layout/overview",
|
||||
|
|
@ -4460,7 +4460,7 @@
|
|||
"pages": [
|
||||
"l/ru/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "Представления",
|
||||
"pages": [
|
||||
"l/ru/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/ru/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -4474,7 +4474,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "Инструкции",
|
||||
"pages": [
|
||||
"l/ru/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/ru/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -4579,7 +4579,7 @@
|
|||
"tab": "Разработчики",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Обзор",
|
||||
"pages": [
|
||||
"l/ru/developers/introduction"
|
||||
]
|
||||
|
|
@ -4632,7 +4632,7 @@
|
|||
"language": "tr",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "Başlarken",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -4661,7 +4661,7 @@
|
|||
"tab": "Kullanıcı Rehberi",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Genel Bakış",
|
||||
"pages": [
|
||||
"l/tr/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -4834,7 +4834,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Layout",
|
||||
"group": "Düzen",
|
||||
"icon": "table-columns",
|
||||
"pages": [
|
||||
"l/tr/user-guide/layout/overview",
|
||||
|
|
@ -4843,7 +4843,7 @@
|
|||
"pages": [
|
||||
"l/tr/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "Görünümler",
|
||||
"pages": [
|
||||
"l/tr/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/tr/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -4857,7 +4857,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "Nasıl Yapılırlar",
|
||||
"pages": [
|
||||
"l/tr/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/tr/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -4962,7 +4962,7 @@
|
|||
"tab": "Geliştiriciler",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "Genel Bakış",
|
||||
"pages": [
|
||||
"l/tr/developers/introduction"
|
||||
]
|
||||
|
|
@ -5015,7 +5015,7 @@
|
|||
"language": "zh",
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Getting Started",
|
||||
"tab": "开始使用",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Welcome",
|
||||
|
|
@ -5044,7 +5044,7 @@
|
|||
"tab": "用户指南",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "概览",
|
||||
"pages": [
|
||||
"l/zh/user-guide/introduction"
|
||||
]
|
||||
|
|
@ -5217,7 +5217,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Layout",
|
||||
"group": "布局",
|
||||
"icon": "table-columns",
|
||||
"pages": [
|
||||
"l/zh/user-guide/layout/overview",
|
||||
|
|
@ -5226,7 +5226,7 @@
|
|||
"pages": [
|
||||
"l/zh/user-guide/layout/capabilities/navigation",
|
||||
{
|
||||
"group": "Views",
|
||||
"group": "视图",
|
||||
"pages": [
|
||||
"l/zh/user-guide/views-pipelines/capabilities/table-views",
|
||||
"l/zh/user-guide/views-pipelines/capabilities/kanban-views",
|
||||
|
|
@ -5240,7 +5240,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "How-Tos",
|
||||
"group": "操作指南",
|
||||
"pages": [
|
||||
"l/zh/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping",
|
||||
"l/zh/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects",
|
||||
|
|
@ -5345,7 +5345,7 @@
|
|||
"tab": "开发者",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"group": "概览",
|
||||
"pages": [
|
||||
"l/zh/developers/introduction"
|
||||
]
|
||||
|
|
@ -5365,7 +5365,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "API",
|
||||
"group": "接口",
|
||||
"pages": [
|
||||
"l/zh/developers/extend/api",
|
||||
"l/zh/developers/extend/webhooks",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: الأوامر الخلفية
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## الأوامر المفيدة
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: الأخطاء والطلبات وطلبات السحب
|
||||
icon: bug
|
||||
info: أبلغ عن المشكلات، واطلب الميزات، وساهم بالشفرة البرمجية
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: أفضل الممارسات
|
||||
icon: star
|
||||
---
|
||||
|
||||
تحدد هذه الوثيقة أفضل الممارسات التي يجب اتباعها عند العمل في الواجهة الأمامية.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: هيكلية المجلدات
|
||||
icon: folder-tree
|
||||
info: نظرة مفصلة على هيكل المجلدات الخاصة بنا
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: أوامر الواجهة الأمامية
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## الأوامر المفيدة
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: دليل الأسلوب
|
||||
icon: paintbrush
|
||||
---
|
||||
|
||||
تشمل هذه الوثيقة القواعد التي يجب اتباعها عند كتابة التعليمات البرمجية.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: الإعداد المحلي
|
||||
icon: laptop-code
|
||||
description: الدليل للمساهمين (أو المطورين الفضوليين) الذين يرغبون في تشغيل Twenty محلياً.
|
||||
---
|
||||
|
||||
|
|
|
|||
77
packages/twenty-docs/l/ar/developers/contribute/commands.mdx
Normal file
77
packages/twenty-docs/l/ar/developers/contribute/commands.mdx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: Commands
|
||||
icon: terminal
|
||||
description: Useful commands for developing Twenty.
|
||||
---
|
||||
|
||||
Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting.
|
||||
|
||||
## Starting the App
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front # Frontend dev server (http://localhost:3001)
|
||||
npx nx start twenty-server # Backend server (http://localhost:3000)
|
||||
npx nx run twenty-server:worker # Background worker
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
```bash
|
||||
npx nx database:reset twenty-server # Reset and seed database
|
||||
npx nx run twenty-server:database:migrate:prod # Run migrations
|
||||
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow> # Generate a migration
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
```bash
|
||||
npx nx lint:diff-with-main twenty-front # Lint changed files (fastest)
|
||||
npx nx lint:diff-with-main twenty-server
|
||||
npx nx lint twenty-front --configuration=fix # Auto-fix
|
||||
```
|
||||
|
||||
## Type Checking
|
||||
|
||||
```bash
|
||||
npx nx typecheck twenty-front
|
||||
npx nx typecheck twenty-server
|
||||
```
|
||||
|
||||
## "الاختبار"
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
npx nx test twenty-front # Jest unit tests
|
||||
npx nx storybook:build twenty-front # Build Storybook
|
||||
npx nx storybook:test twenty-front # Storybook tests
|
||||
|
||||
# Backend
|
||||
npx nx run twenty-server:test:unit # Unit tests
|
||||
npx nx run twenty-server:test:integration # Integration tests
|
||||
npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset
|
||||
|
||||
# Single file (fastest)
|
||||
npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs
|
||||
```
|
||||
|
||||
## جراف كيو إل
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate # Regenerate types
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema
|
||||
```
|
||||
|
||||
## "الترجمات"
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract # Extract strings
|
||||
npx nx run twenty-front:lingui:compile # Compile translations
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npx nx build twenty-shared # Must be built first
|
||||
npx nx build twenty-front
|
||||
npx nx build twenty-server
|
||||
```
|
||||
176
packages/twenty-docs/l/ar/developers/contribute/style-guide.mdx
Normal file
176
packages/twenty-docs/l/ar/developers/contribute/style-guide.mdx
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: دليل الأسلوب
|
||||
icon: paintbrush
|
||||
description: Code conventions and best practices for contributing to Twenty.
|
||||
---
|
||||
|
||||
## React
|
||||
|
||||
### Functional components only
|
||||
|
||||
Always use TSX functional components with named exports.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const MyComponent = () => {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
export default MyComponent;
|
||||
|
||||
// ✅ Good
|
||||
export function MyComponent() {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### الإزاحة
|
||||
|
||||
Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`.
|
||||
|
||||
```tsx
|
||||
type MyComponentProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||||
```
|
||||
|
||||
### No single-variable prop spreading
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
|
||||
|
||||
// ✅ Good
|
||||
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
|
||||
```
|
||||
|
||||
## "إدارة الحالة"
|
||||
|
||||
### Jotai atoms for global state
|
||||
|
||||
```tsx
|
||||
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
|
||||
export const myAtomState = createAtomState<string>({
|
||||
key: 'myAtomState',
|
||||
defaultValue: 'default value',
|
||||
});
|
||||
```
|
||||
|
||||
* Prefer atoms over prop drilling
|
||||
* Don't use `useRef` for state — use `useState` or atoms
|
||||
* Use atom families and selectors for lists
|
||||
|
||||
### Avoid unnecessary re-renders
|
||||
|
||||
* Extract `useEffect` and data fetching into sibling sidecar components
|
||||
* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect`
|
||||
* Don't use `React.memo()` — fix the root cause instead
|
||||
* Limit `useCallback` / `useMemo` usage
|
||||
|
||||
```tsx
|
||||
// ❌ Bad — useEffect in the same component causes re-renders
|
||||
export const Page = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [dep] = useAtomState(depState);
|
||||
useEffect(() => { setData(dep); }, [dep]);
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
|
||||
// ✅ Good — extract into sibling
|
||||
export const PageData = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [dep] = useAtomState(depState);
|
||||
useEffect(() => { setData(dep); }, [dep]);
|
||||
return <></>;
|
||||
};
|
||||
export const Page = () => {
|
||||
const [data] = useAtomState(dataState);
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
* **`type` over `interface`** — more flexible, easier to compose
|
||||
* **String literals over enums** — except for GraphQL codegen enums and internal library APIs
|
||||
* **No `any`** — strict TypeScript enforced
|
||||
* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`)
|
||||
* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects
|
||||
|
||||
## JavaScript
|
||||
|
||||
```tsx
|
||||
// Use nullish-coalescing (??) instead of ||
|
||||
const value = process.env.MY_VALUE ?? 'default';
|
||||
|
||||
// Use optional chaining
|
||||
onClick?.();
|
||||
```
|
||||
|
||||
## التسمية
|
||||
|
||||
* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`)
|
||||
* **Constants**: SCREAMING_SNAKE_CASE
|
||||
* **Types/Classes**: PascalCase
|
||||
* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`)
|
||||
* **Event handlers**: `handleClick` (not `onClick` for the handler function)
|
||||
* **Component props**: prefix with component name (`ButtonProps`)
|
||||
* **Styled components**: prefix with `Styled` (`StyledTitle`)
|
||||
|
||||
## التنسيق
|
||||
|
||||
Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const StyledButton = styled.button`
|
||||
color: #333333;
|
||||
font-size: 1rem;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
// ✅ Good
|
||||
const StyledButton = styled.button`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
```
|
||||
|
||||
## استيرادات
|
||||
|
||||
Use aliases instead of relative paths:
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
import { Foo } from '../../../../../testing/decorators/Foo';
|
||||
|
||||
// ✅ Good
|
||||
import { Foo } from '~/testing/decorators/Foo';
|
||||
import { Bar } from '@/modules/bar/components/Bar';
|
||||
```
|
||||
|
||||
## Folder Structure
|
||||
|
||||
```
|
||||
front
|
||||
└── modules/ # Feature modules
|
||||
│ └── module1/
|
||||
│ ├── components/
|
||||
│ ├── constants/
|
||||
│ ├── contexts/
|
||||
│ ├── graphql/ (fragments, queries, mutations)
|
||||
│ ├── hooks/
|
||||
│ ├── states/ (atoms, selectors)
|
||||
│ ├── types/
|
||||
│ └── utils/
|
||||
└── pages/ # Route-level components
|
||||
└── ui/ # Reusable UI components (display, input, feedback, ...)
|
||||
```
|
||||
|
||||
* Modules can import from other modules, but `ui/` should stay dependency-free
|
||||
* Use `internal/` subfolders for module-private code
|
||||
* Components under 300 lines, services under 500 lines
|
||||
|
|
@ -1,147 +1,55 @@
|
|||
---
|
||||
title: واجهات برمجة التطبيقات
|
||||
description: استعلم وعدّل بيانات إدارة علاقات العملاء (CRM) لديك برمجياً باستخدام REST أو GraphQL.
|
||||
icon: plug
|
||||
description: REST and GraphQL APIs generated from your workspace schema.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
تم تصميم Twenty ليكون صديقًا للمطورين، حيث يوفر واجهات برمجة قوية تتكيف مع نموذج البيانات المخصص. نحن نوفر أربعة أنواع متميزة من واجهات برمجة التطبيقات لتلبية احتياجات التكامل المختلفة.
|
||||
## Schema-per-tenant APIs
|
||||
|
||||
## نهج المطوّر أولاً
|
||||
There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs.
|
||||
|
||||
تقوم Twenty بإنشاء واجهات برمجة التطبيقات خصيصاً لنموذج بياناتك:
|
||||
Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data.
|
||||
|
||||
* **لا حاجة إلى معرفات طويلة**: استخدم أسماء الكائنات والحقول مباشرة في نقاط النهاية
|
||||
* **معاملة متساوية للكائنات القياسية والمخصصة**: تحصل كائناتك المخصصة على نفس معاملة واجهة برمجة التطبيقات كما هو الحال مع الكائنات المضمنة
|
||||
* **نقاط نهاية مخصصة**: يحصل كل كائن وحقل على نقطة نهاية API الخاصة به
|
||||
* **وثائق مخصصة**: يتم إنشاؤها خصيصًا لنموذج بيانات مساحة عملك
|
||||
## Two APIs
|
||||
|
||||
<Note>
|
||||
وثائق واجهة برمجة التطبيقات المخصصة لك متاحة ضمن **الإعدادات → واجهات برمجة التطبيقات وخطافات الويب** بعد إنشاء مفتاح API. نظرًا لأن Twenty تُنشئ واجهات برمجة تطبيقات تتطابق مع نموذج البيانات المخصص لديك، فإن الوثائق فريدة لمساحة عملك.
|
||||
</Note>
|
||||
**Core API** — `/rest/` and `/graphql/`
|
||||
|
||||
## نوعا واجهات برمجة التطبيقات
|
||||
CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations.
|
||||
|
||||
### واجهة برمجة التطبيقات الأساسية
|
||||
**Metadata API** — `/rest/metadata/` and `/metadata/`
|
||||
|
||||
يتم الوصول إليها عبر `/rest/` أو `/graphql/`
|
||||
Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model.
|
||||
|
||||
تعامَل مع **السجلات** الفعلية لديك (البيانات):
|
||||
Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way.
|
||||
|
||||
* إنشاء وقراءة وتحديث وحذف الأشخاص والشركات والفرص، إلخ.
|
||||
* استعلام وتصفية البيانات
|
||||
* إدارة العلاقات بين السجلات
|
||||
## Base URLs
|
||||
|
||||
### واجهة برمجة البيانات الوصفية
|
||||
|
||||
يتم الوصول إليها عبر `/rest/metadata/` أو `/metadata/`
|
||||
|
||||
إدارة **مساحة العمل ونموذج البيانات** لديك:
|
||||
|
||||
* إنشاء أو تعديل أو حذف الكائنات والحقول
|
||||
* تكوين إعدادات مساحة العمل
|
||||
* تعريف العلاقات بين الكائنات
|
||||
|
||||
## REST مقابل GraphQL
|
||||
|
||||
تتوفر واجهات برمجة التطبيقات الأساسية وواجهات البيانات الوصفية بصيغتي REST وGraphQL:
|
||||
|
||||
| التنسيق | العمليات المتاحة |
|
||||
| ----------- | ----------------------------------------------------------------------------- |
|
||||
| **REST** | CRUD، عمليات الدفعات، إدراج/تحديث |
|
||||
| **GraphQL** | نفس الشيء + **عمليات إدراج/تحديث مجمعة**، واستعلامات العلاقات في استدعاء واحد |
|
||||
|
||||
اختر بناءً على احتياجاتك — كلا الصيغتين تصلان إلى البيانات نفسها.
|
||||
|
||||
## نقاط نهاية API
|
||||
|
||||
| البيئة | عنوان URL الأساسي |
|
||||
| --------------------- | ------------------------- |
|
||||
| **السحابة** | `https://api.twenty.com/` |
|
||||
| **الاستضافة الذاتية** | `https://{your-domain}/` |
|
||||
| البيئة | عنوان URL الأساسي |
|
||||
| ----------- | ------------------------- |
|
||||
| Cloud | `https://api.twenty.com/` |
|
||||
| Self-Hosted | `https://{your-domain}/` |
|
||||
|
||||
## المصادقة
|
||||
|
||||
كل طلب API يتطلب تضمين مفتاح API في رأس الطلب:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
### قم بإنشاء مفتاح API
|
||||
|
||||
1. انتقل إلى **الإعدادات → واجهات برمجة التطبيقات وخطافات الويب**
|
||||
2. انقر على **+ إنشاء مفتاح**
|
||||
3. التكوين:
|
||||
* **الاسم**: اسم وصفي للمفتاح
|
||||
* **تاريخ الانتهاء**: متى تنتهي صلاحية المفتاح
|
||||
4. انقر على **حفظ**
|
||||
5. **انسخه فوراً** — يظهر المفتاح مرة واحدة فقط
|
||||
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access.
|
||||
|
||||
<VimeoEmbed videoId="928786722" title="إنشاء مفتاح API" />
|
||||
|
||||
<Warning>
|
||||
يمنح مفتاح API الخاص بك الوصول إلى بيانات حساسة. لا تشاركه مع خدمات غير موثوقة. إذا تم اختراقه، عطّلْه فوراً وأنشئ مفتاحاً جديداً.
|
||||
</Warning>
|
||||
For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/ar/developers/extend/oauth).
|
||||
|
||||
### تعيين دور لمفتاح API
|
||||
## Batch operations
|
||||
|
||||
لتحسين الأمان، عيّن دوراً محدداً لتقييد الوصول:
|
||||
Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`.
|
||||
|
||||
1. اذهب إلى **الإعدادات → الأدوار**
|
||||
2. انقر على الدور الذي ترغب في تعيينه
|
||||
3. افتح علامة التبويب **التعيين**
|
||||
4. ضمن **مفاتيح API**، انقر على **+ تعيين إلى مفتاح API**
|
||||
5. حدد مفتاح API
|
||||
## Rate limits
|
||||
|
||||
سيرث المفتاح أذونات ذلك الدور. راجع [الأذونات](/l/ar/user-guide/permissions-access/capabilities/permissions) للحصول على التفاصيل.
|
||||
|
||||
### إدارة مفاتيح API
|
||||
|
||||
**إعادة التوليد**: الإعدادات → واجهات برمجة التطبيقات وخطافات الويب → انقر على المفتاح → **إعادة التوليد**
|
||||
|
||||
**حذف**: الإعدادات → واجهات برمجة التطبيقات وخطافات الويب → انقر على المفتاح → **حذف**
|
||||
|
||||
## ملعب واجهة برمجة التطبيقات
|
||||
|
||||
اختبر واجهات برمجة التطبيقات لديك مباشرة في المتصفح باستخدام الملعب المدمج لدينا — متاح لكلٍ من **REST** و**GraphQL**.
|
||||
|
||||
### الوصول إلى الملعب
|
||||
|
||||
1. انتقل إلى **الإعدادات → واجهات برمجة التطبيقات وخطافات الويب**
|
||||
2. أنشئ مفتاح API (مطلوب)
|
||||
3. انقر على **REST API** أو **GraphQL API** لفتح الملعب
|
||||
|
||||
### ما الذي ستحصل عليه
|
||||
|
||||
* **وثائق تفاعلية**: يتم إنشاؤها لنموذج البيانات المحدد لديك
|
||||
* **اختبارات حيّة**: تنفيذ استدعاءات API فعلية على مساحة عملك
|
||||
* **مستكشف المخطط**: تصفح الكائنات والحقول والعلاقات المتاحة
|
||||
* **منشئ الطلبات**: أنشئ الاستعلامات مع الإكمال التلقائي
|
||||
|
||||
يعكس الملعب الكائنات والحقول المخصصة لديك، لذا تكون الوثائق دائماً دقيقة لمساحة عملك.
|
||||
|
||||
## عمليات الدفعات
|
||||
|
||||
كلٌ من REST وGraphQL يدعمان عمليات الدفعات:
|
||||
|
||||
* **حجم الدفعة**: حتى 60 سجل لكل طلب
|
||||
* **العمليات**: إنشاء وتحديث وحذف سجلات متعددة
|
||||
|
||||
**ميزات خاصة بـ GraphQL:**
|
||||
|
||||
* **إدراج/تحديث دفعي**: إنشاء أو تحديث في استدعاء واحد
|
||||
* استخدم الأسماء الجمع للكائنات (على سبيل المثال، `CreateCompanies` بدلاً من `CreateCompany`)
|
||||
|
||||
## حدود المعدل
|
||||
|
||||
يتم تنظيم طلبات API لضمان استقرار المنصة:
|
||||
|
||||
| الحد | القيمة |
|
||||
| -------------- | ---------------------- |
|
||||
| **الطلبات** | 100 استدعاء في الدقيقة |
|
||||
| **حجم الدفعة** | 60 سجل لكل استدعاء |
|
||||
|
||||
<Tip>
|
||||
استخدم عمليات الدفعات لزيادة الإنتاجية — عالج ما يصل إلى 60 سجلًا في استدعاء API واحد بدلاً من إجراء طلبات فردية.
|
||||
</Tip>
|
||||
| الحد | القيمة |
|
||||
| ---------- | ------------------ |
|
||||
| Requests | 100 per minute |
|
||||
| Batch size | 60 سجل لكل استدعاء |
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,434 @@
|
|||
---
|
||||
title: CLI & Testing
|
||||
description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## الأصول العامة (مجلد `public/`)
|
||||
|
||||
يحتوي مجلد `public/` في جذر تطبيقك على ملفات ثابتة — صور وأيقونات وخطوط وأي أصول أخرى يحتاجها تطبيقك وقت التشغيل. تُدرج هذه الملفات تلقائيًا في عمليات البناء، وتُزامَن أثناء وضع التطوير، وتُرفَع إلى الخادم.
|
||||
|
||||
الملفات الموضوعة في `public/` هي:
|
||||
|
||||
* **متاحة للعامة** — بمجرد مزامنتها إلى الخادم، تُقدَّم الأصول عبر عنوان URL عام. لا يلزم توثيق للوصول إليها.
|
||||
* **متاحة في المكوّنات الأمامية** — استخدم عناوين الأصول لعرض الصور أو الأيقونات أو أي وسائط داخل مكوّنات React لديك.
|
||||
* **متاحة في الدوال المنطقية** — أشِر إلى عناوين الأصول في رسائل البريد الإلكتروني أو استجابات واجهات البرمجة أو أي منطق على جهة الخادم.
|
||||
* **مستخدمة لبيانات تعريف السوق** — يشير حقلا `logoUrl` و`screenshots` في `defineApplication()` إلى ملفات من هذا المجلد (مثل `public/logo.png`). تُعرَض هذه عند نشر تطبيقك في السوق.
|
||||
* **تُزامَن تلقائيًا في وضع التطوير** — عند إضافة ملف في `public/` أو تحديثه أو حذفه، تتم مزامنته إلى الخادم تلقائيًا. لا حاجة لإعادة التشغيل.
|
||||
* **مضمَّنة في عمليات البناء** — يقوم `yarn twenty build` بتجميع جميع الأصول العامة ضمن مخرجات التوزيع.
|
||||
|
||||
### الوصول إلى الأصول العامة باستخدام `getPublicAssetUrl`
|
||||
|
||||
استخدم المساعد `getPublicAssetUrl` من `twenty-sdk` للحصول على العنوان الكامل لملف في دليل `public/` لديك. يعمل ذلك في كلٍ من الدوال المنطقية والمكوّنات الأمامية.
|
||||
|
||||
**في دالة منطقية:**
|
||||
|
||||
```ts src/logic-functions/send-invoice.ts
|
||||
import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (): Promise<any> => {
|
||||
const logoUrl = getPublicAssetUrl('logo.png');
|
||||
const invoiceUrl = getPublicAssetUrl('templates/invoice.png');
|
||||
|
||||
// Fetch the file content (no auth required — public endpoint)
|
||||
const response = await fetch(invoiceUrl);
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
return { logoUrl, size: buffer.byteLength };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-...',
|
||||
name: 'send-invoice',
|
||||
description: 'Sends an invoice with the app logo',
|
||||
timeoutSeconds: 10,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**في مكوّن أمامي:**
|
||||
|
||||
```tsx src/front-components/company-card.tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
export default defineFrontComponent(() => {
|
||||
const logoUrl = getPublicAssetUrl('logo.png');
|
||||
|
||||
return <img src={logoUrl} alt="App logo" />;
|
||||
});
|
||||
```
|
||||
|
||||
وسيطة `path` نسبية إلى مجلد `public/` الخاص بتطبيقك. كلٌّ من `getPublicAssetUrl('logo.png')` و`getPublicAssetUrl('public/logo.png')` يُحلاّن إلى العنوان نفسه — تتم إزالة بادئة `public/` تلقائيًا إن وُجدت.
|
||||
|
||||
## استخدام حِزَم npm
|
||||
|
||||
يمكنك تثبيت واستخدام أي حزمة npm في تطبيقك. يتم تجميع كلٍ من الدوال المنطقية والمكوّنات الأمامية باستخدام [esbuild](https://esbuild.github.io/)، والذي يُضمّن جميع التبعيات ضمن المخرجات — لا حاجة إلى `node_modules` وقت التشغيل.
|
||||
|
||||
### تثبيت حزمة
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add axios
|
||||
```
|
||||
|
||||
ثم استوردها في شيفرتك:
|
||||
|
||||
```ts src/logic-functions/fetch-data.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import axios from 'axios';
|
||||
|
||||
const handler = async (): Promise<any> => {
|
||||
const { data } = await axios.get('https://api.example.com/data');
|
||||
|
||||
return { data };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '...',
|
||||
name: 'fetch-data',
|
||||
description: 'Fetches data from an external API',
|
||||
timeoutSeconds: 10,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
وينطبق الأمر نفسه على المكوّنات الأمامية:
|
||||
|
||||
```tsx src/front-components/chart.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
const DateWidget = () => {
|
||||
return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'date-widget',
|
||||
component: DateWidget,
|
||||
});
|
||||
```
|
||||
|
||||
### كيف يعمل التجميع
|
||||
|
||||
تستخدم خطوة البناء أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية ولكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة.
|
||||
|
||||
**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت.
|
||||
|
||||
**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح.
|
||||
|
||||
كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم.
|
||||
|
||||
## اختبار تطبيقك
|
||||
|
||||
يوفّر SDK واجهات برمجة قابلة للتنفيذ برمجيًا تمكّنك من بناء تطبيقك ونشره وتثبيته وإلغاء تثبيته من شيفرة الاختبار. بالاقتران مع [Vitest](https://vitest.dev/) وعملاء واجهة البرمجة مضبوطي الأنواع، يمكنك كتابة اختبارات تكامل تتحقّق من أن تطبيقك يعمل من البداية إلى النهاية مقابل خادم Twenty حقيقي.
|
||||
|
||||
### إعداد
|
||||
|
||||
يتضمّن التطبيق المُولَّد بالقالب بالفعل Vitest. إذا أعددته يدويًا، فثبّت التبعيات:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D vitest vite-tsconfig-paths
|
||||
```
|
||||
|
||||
أنشئ `vitest.config.ts` في جذر تطبيقك:
|
||||
|
||||
```ts vitest.config.ts
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
projects: ['tsconfig.spec.json'],
|
||||
ignoreConfigErrors: true,
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
testTimeout: 120_000,
|
||||
hookTimeout: 120_000,
|
||||
include: ['src/**/*.integration-test.ts'],
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:2020',
|
||||
TWENTY_API_KEY: 'your-api-key',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
أنشئ ملف إعداد يتحقّق من إمكانية الوصول إلى الخادم قبل تشغيل الاختبارات:
|
||||
|
||||
```ts src/__tests__/setup-test.ts
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
|
||||
beforeAll(async () => {
|
||||
// Verify the server is running
|
||||
const response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||||
'Start the server before running integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
// Write a temporary config for the SDK
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||||
JSON.stringify({
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
}, null, 2),
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### واجهات SDK البرمجية
|
||||
|
||||
يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار:
|
||||
|
||||
| دالة | الوصف |
|
||||
| -------------- | ----------------------------------------- |
|
||||
| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball |
|
||||
| `appDeploy` | رفع ملف tarball إلى الخادم |
|
||||
| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة |
|
||||
| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة |
|
||||
|
||||
تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`.
|
||||
|
||||
### كتابة اختبار تكامل
|
||||
|
||||
إليك مثالًا كاملًا يبني التطبيق وينشره ويثبّته، ثم يتحقّق من ظهوره في مساحة العمل:
|
||||
|
||||
```ts src/__tests__/app-install.integration-test.ts
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||||
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
const APP_PATH = process.cwd();
|
||||
|
||||
describe('App installation', () => {
|
||||
beforeAll(async () => {
|
||||
const buildResult = await appBuild({
|
||||
appPath: APP_PATH,
|
||||
tarball: true,
|
||||
onProgress: (message: string) => console.log(`[build] ${message}`),
|
||||
});
|
||||
|
||||
if (!buildResult.success) {
|
||||
throw new Error(`Build failed: ${buildResult.error?.message}`);
|
||||
}
|
||||
|
||||
const deployResult = await appDeploy({
|
||||
tarballPath: buildResult.data.tarballPath!,
|
||||
onProgress: (message: string) => console.log(`[deploy] ${message}`),
|
||||
});
|
||||
|
||||
if (!deployResult.success) {
|
||||
throw new Error(`Deploy failed: ${deployResult.error?.message}`);
|
||||
}
|
||||
|
||||
const installResult = await appInstall({ appPath: APP_PATH });
|
||||
|
||||
if (!installResult.success) {
|
||||
throw new Error(`Install failed: ${installResult.error?.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await appUninstall({ appPath: APP_PATH });
|
||||
});
|
||||
|
||||
it('should find the installed app in the workspace', async () => {
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const result = await metadataClient.query({
|
||||
findManyApplications: {
|
||||
id: true,
|
||||
name: true,
|
||||
universalIdentifier: true,
|
||||
},
|
||||
});
|
||||
|
||||
const installedApp = result.findManyApplications.find(
|
||||
(app: { universalIdentifier: string }) =>
|
||||
app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
);
|
||||
|
||||
expect(installedApp).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### تشغيل الاختبارات
|
||||
|
||||
تأكّد من تشغيل خادم Twenty المحلي لديك، ثم:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn test
|
||||
```
|
||||
|
||||
أو في وضع المراقبة أثناء التطوير:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn test:watch
|
||||
```
|
||||
|
||||
### التحقق من الأنواع
|
||||
|
||||
يمكنك أيضًا تشغيل التحقق من الأنواع على تطبيقك دون تشغيل الاختبارات:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty typecheck
|
||||
```
|
||||
|
||||
يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع.
|
||||
|
||||
## مرجع CLI
|
||||
|
||||
بالإضافة إلى `dev` و`build` و`add` و`typecheck`، يوفّر CLI أوامر لتنفيذ الدوال وعرض السجلات وإدارة تثبيتات التطبيقات.
|
||||
|
||||
### تنفيذ الدوال (`yarn twenty exec`)
|
||||
|
||||
تشغيل دالة منطقية يدويًا دون تشغيلها عبر HTTP أو cron أو حدث قاعدة بيانات:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Execute by function name
|
||||
yarn twenty exec -n create-new-post-card
|
||||
|
||||
# Execute by universalIdentifier
|
||||
yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
|
||||
# Pass a JSON payload
|
||||
yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
### عرض سجلات الدوال (`yarn twenty logs`)
|
||||
|
||||
بثّ سجلات التنفيذ لدوال تطبيقك المنطقية:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Stream all function logs
|
||||
yarn twenty logs
|
||||
|
||||
# Filter by function name
|
||||
yarn twenty logs -n create-new-post-card
|
||||
|
||||
# Filter by universalIdentifier
|
||||
yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
<Note>
|
||||
يختلف هذا عن `yarn twenty server logs`، الذي يعرض سجلات حاوية Docker. يعرض `yarn twenty logs` سجلات تنفيذ دوال تطبيقك من خادم Twenty.
|
||||
</Note>
|
||||
|
||||
### إلغاء تثبيت تطبيق (`yarn twenty uninstall`)
|
||||
|
||||
أزل تطبيقك من مساحة العمل النشطة:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty uninstall
|
||||
|
||||
# Skip the confirmation prompt
|
||||
yarn twenty uninstall --yes
|
||||
```
|
||||
|
||||
## إدارة الريموتات
|
||||
|
||||
**الريموت** هو خادم Twenty يتصل به تطبيقك. أثناء الإعداد، تُنشئ أداة إنشاء الهيكل واحدًا لك تلقائيًا. يمكنك إضافة ريموتات أخرى أو التبديل بينها في أي وقت.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`.
|
||||
|
||||
## التكامل المستمر (CI) باستخدام GitHub Actions
|
||||
|
||||
تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب.
|
||||
|
||||
سير العمل:
|
||||
|
||||
1. يجلب الشيفرة الخاصة بك
|
||||
2. يشغّل خادم Twenty مؤقتًا باستخدام الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. يثبّت التبعيات باستخدام `yarn install --immutable`
|
||||
4. يشغّل `yarn test` مع حقن `TWENTY_API_URL` و`TWENTY_API_KEY` من مخرجات الإجراء
|
||||
|
||||
```yaml .github/workflows/ci.yml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
```
|
||||
|
||||
لا تحتاج إلى تهيئة أي أسرار — إذ يبدأ إجراء `spawn-twenty-docker-image` خادم Twenty عابرًا مباشرة في المشغّل ويُخرِج تفاصيل الاتصال. يتم توفير السر `GITHUB_TOKEN` تلقائيًا من قِبل GitHub.
|
||||
|
||||
لتثبيت إصدار محدّد من Twenty بدلًا من `latest`، غيّر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل.
|
||||
494
packages/twenty-docs/l/ar/developers/extend/apps/data-model.mdx
Normal file
494
packages/twenty-docs/l/ar/developers/extend/apps/data-model.mdx
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
---
|
||||
title: نموذج البيانات
|
||||
description: Define objects, fields, roles, and application metadata with the Twenty SDK.
|
||||
icon: database
|
||||
---
|
||||
|
||||
The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. يجب عليك استخدام `export default defineEntity({...})` لكي يكتشف SDK الكيانات الخاصة بك. تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
|
||||
|
||||
<Note>
|
||||
**تنظيم الملفات يعود إليك.**
|
||||
يعتمد اكتشاف الكيانات على AST — حيث يعثر SDK على استدعاءات `export default defineEntity(...)` بغض النظر عن مكان وجود الملف. تجميع الملفات حسب النوع (مثلًا، `logic-functions/` و`roles/`) هو مجرّد عرف، وليس متطلبًا.
|
||||
</Note>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineRole" description="تهيئة صلاحيات الدور والوصول إلى الكائنات">
|
||||
|
||||
تُغلّف الأدوار الصلاحيات على كائنات وإجراءات مساحة العمل لديك.
|
||||
|
||||
```ts restricted-company-role.ts
|
||||
import {
|
||||
defineRole,
|
||||
PermissionFlag,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
|
||||
label: 'My new role',
|
||||
description: 'A role that can be used in your workspace',
|
||||
canReadAllObjectRecords: false,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
canDestroyObjectRecords: false,
|
||||
},
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
],
|
||||
permissionFlags: [PermissionFlag.APPLICATIONS],
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineApplication" description="تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق)">
|
||||
|
||||
يجب أن يحتوي كل تطبيق على استدعاء واحد فقط لـ `defineApplication` يصف:
|
||||
|
||||
* **الهوية**: المعرّفات، اسم العرض، والوصف.
|
||||
* **الأذونات**: أيُّ دورٍ تستخدمه وظائفه ومكوّناته الأمامية.
|
||||
* **(اختياري) المتغيرات**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
|
||||
* **(اختياري) دوال ما قبل التثبيت/ما بعد التثبيت**: دوال منطقية تعمل قبل التثبيت أو بعده.
|
||||
|
||||
```ts src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
الملاحظات:
|
||||
* حقول `universalIdentifier` هي معرّفات حتمية تملكها أنت. أنشِئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
|
||||
* `applicationVariables` تصبح متغيرات بيئة لوظائفك ومكوّناتك الأمامية (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` يجب أن يُشير إلى دور مُعرَّف باستخدام `defineRole()` (انظر أعلاه).
|
||||
* يتم اكتشاف دوال ما قبل التثبيت وما بعده تلقائيًا أثناء بناء البيان — لا حاجة للإشارة إليها في `defineApplication()`.
|
||||
|
||||
#### بيانات التعريف لسوق التطبيقات
|
||||
|
||||
إذا كنت تخطط لـ [نشر تطبيقك](/l/ar/developers/extend/apps/publishing)، فإن هذه الحقول الاختيارية تتحكّم في كيفية ظهوره في السوق:
|
||||
|
||||
| الحقل | الوصف |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `author` | اسم المؤلف أو الشركة |
|
||||
| `category` | فئة التطبيق لتصفية سوق التطبيقات |
|
||||
| `logoUrl` | مسار شعار تطبيقك (مثلًا، `public/logo.png`) |
|
||||
| `screenshots` | مصفوفة لمسارات لقطات الشاشة (مثلًا، `public/screenshot-1.png`) |
|
||||
| `aboutDescription` | وصف ماركداون أطول لعلامة التبويب "حول". إذا لم يتم تضمينه، يستخدم السوق ملف `README.md` الخاص بالحزمة من npm |
|
||||
| `websiteUrl` | رابط إلى موقعك الإلكتروني |
|
||||
| `termsUrl` | رابط إلى شروط الخدمة |
|
||||
| `emailSupport` | عنوان البريد الإلكتروني للدعم |
|
||||
| `issueReportUrl` | رابط إلى متتبّع المشاكل |
|
||||
|
||||
#### الأدوار والصلاحيات
|
||||
|
||||
يُحدّد الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق والمكوّنات الأمامية في تطبيقك. راجع `defineRole` أعلاه للحصول على التفاصيل.
|
||||
|
||||
* رمز وقت التشغيل المحقون باسم `TWENTY_APP_ACCESS_TOKEN` مستمد من هذا الدور.
|
||||
* العميل مضبوط الأنواع مقيَّد بالأذونات الممنوحة لذلك الدور.
|
||||
* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا يضم فقط الأذونات التي تحتاجها وظائفك.
|
||||
|
||||
##### الدور الافتراضي للوظيفة
|
||||
|
||||
عند توليد تطبيق جديد بالقالب، ينشئ CLI ملفّ دور افتراضي:
|
||||
|
||||
```ts src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [],
|
||||
fieldPermissions: [],
|
||||
permissionFlags: [],
|
||||
});
|
||||
```
|
||||
|
||||
يُشار إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`:
|
||||
|
||||
* **\*.role.ts** يحدد ما يمكن أن يفعله الدور.
|
||||
* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته.
|
||||
|
||||
الملاحظات:
|
||||
* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز.
|
||||
* استبدل `objectPermissions` و`fieldPermissions` بالكائنات والحقول التي تحتاجها وظائفك فعليًا.
|
||||
* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في حدّها الأدنى.
|
||||
* اطّلع على مثال عملي: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineObject" description="تعريف كائنات مخصصة مع حقول">
|
||||
|
||||
تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج:
|
||||
|
||||
```ts postCard.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,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* استخدم `defineObject()` للحصول على تحقق مدمج ودعم أفضل من IDE.
|
||||
* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر.
|
||||
* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به.
|
||||
* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة.
|
||||
* يمكنك إنشاء كائنات جديدة باستخدام `yarn twenty add`، والذي يرشدك خلال التسمية والحقول والعلاقات.
|
||||
|
||||
<Note>
|
||||
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية
|
||||
مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt`.
|
||||
لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
|
||||
يمكنك تجاوز الحقول الافتراضية من خلال تعريف حقل بالاسم نفسه في مصفوفة `fields` الخاصة بك،
|
||||
لكن هذا غير مستحسن.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineField — الحقول القياسية" description="وسّع الكائنات الموجودة بحقول إضافية">
|
||||
|
||||
استخدم `defineField()` لإضافة حقول إلى كائنات لا تملكها — مثل كائنات Twenty القياسية (Person, Company, etc.) أو كائنات من تطبيقات أخرى. على خلاف الحقول المضمّنة في `defineObject()`، تتطلّب الحقول المستقلة `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` يحدّد الكائن الهدف. بالنسبة للكائنات القياسية، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` المُصدَّر من `twenty-sdk`.
|
||||
* عند تعريف الحقول بشكل مضمّن في `defineObject()`، **لا** تحتاج إلى `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب.
|
||||
* `defineField()` هي الطريقة الوحيدة لإضافة حقول إلى كائنات لم تُنشئها باستخدام `defineObject()`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineField — حقول العلاقات" description="وصِل الكائنات معًا بعلاقات ثنائية الاتجاه">
|
||||
|
||||
تربط العلاقات الكائنات معًا. في 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** (جانب "الواحد"):
|
||||
|
||||
```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** (جانب "العديد" — يحمل المفتاح الخارجي):
|
||||
|
||||
```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>
|
||||
|
||||
#### الربط مع الكائنات القياسية
|
||||
|
||||
لإنشاء علاقة مع كائن Twenty مضمّن (Person, Company, etc.)، استخدم `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`) |
|
||||
|
||||
#### حقول العلاقات المضمّنة في defineObject
|
||||
|
||||
يمكنك أيضًا تعريف حقول العلاقات مباشرةً داخل `defineObject()`. في هذه الحالة، احذف `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
|
||||
],
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## توليد قوالب الكيانات باستخدام `yarn twenty add`
|
||||
|
||||
بدلًا من إنشاء ملفات الكيانات يدويًا، يمكنك استخدام أداة القوالب التفاعلية:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add
|
||||
```
|
||||
|
||||
ستطالبك باختيار نوع الكيان وتُرشدك خلال الحقول المطلوبة. تُولّد ملفًا جاهزًا للاستخدام مع `universalIdentifier` ثابت واستدعاء `defineEntity()` الصحيح.
|
||||
|
||||
يمكنك أيضًا تمرير نوع الكيان مباشرة لتخطي المطالبة الأولى:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add object
|
||||
yarn twenty add logicFunction
|
||||
yarn twenty add frontComponent
|
||||
```
|
||||
|
||||
### أنواع الكيانات المتاحة
|
||||
|
||||
| نوع الكيان | أمر | الملف المُولَّد |
|
||||
| ------------------ | ------------------------------------ | ------------------------------------------------------- |
|
||||
| كائن | `yarn twenty add object` | `src/objects/\<name>.ts` |
|
||||
| الحقل | `yarn twenty add field` | `src/fields/\<name>.ts` |
|
||||
| دالة منطقية | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
|
||||
| مكوّن أمامي | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
|
||||
| دور | `yarn twenty add role` | `src/roles/\<name>.ts` |
|
||||
| مهارة | `yarn twenty add skill` | `src/skills/\<name>.ts` |
|
||||
| وكيل | `yarn twenty add agent` | `src/agents/\<name>.ts` |
|
||||
| عرض | `yarn twenty add view` | `src/views/\<name>.ts` |
|
||||
| عنصر قائمة التنقّل | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
|
||||
| تخطيط الصفحة | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
|
||||
|
||||
### ما الذي تُنشئه أداة القوالب
|
||||
|
||||
لكل نوع كيان قالب خاص به. على سبيل المثال، يسأل `yarn twenty add object` عن:
|
||||
|
||||
1. **الاسم (مفرد)** — مثل `invoice`
|
||||
2. **الاسم (جمع)** — مثل `invoices`
|
||||
3. **التسمية (مفرد)** — تُستمد تلقائيًا من الاسم (مثل `Invoice`)
|
||||
4. **التسمية (جمع)** — تُملأ تلقائيًا (مثل `Invoices`)
|
||||
5. **إنشاء عرض وعنصر تنقّل؟** — إذا أجبت بنعم، فستُنشئ أداة القوالب أيضًا عرضًا مطابقًا ورابط شريط جانبي للكائن الجديد.
|
||||
|
||||
أنواع الكيانات الأخرى لها مطالبات أبسط — فمعظمها يطلب اسمًا فقط.
|
||||
|
||||
نوع الكيان `field` أكثر تفصيلاً: يطلب اسم الحقل وتسمية الحقل ونوعه (من قائمة بكل أنواع الحقول المتاحة مثل `TEXT` و`NUMBER` و`SELECT` و`RELATION` وغيرها)، ومعرّف `universalIdentifier` للكائن الهدف.
|
||||
|
||||
### مسار خرج مخصّص
|
||||
|
||||
استخدم العلم `--path` لوضع الملف المُولَّد في موقع مخصّص:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add logicFunction --path src/custom-folder
|
||||
```
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
---
|
||||
title: المكوّنات الأمامية
|
||||
description: Build React components that render inside Twenty's UI with sandboxed isolation.
|
||||
icon: window-maximize
|
||||
---
|
||||
|
||||
المكوّنات الأمامية هي مكوّنات React تُعرَض مباشرة داخل واجهة مستخدم Twenty. تعمل ضمن **Web Worker** معزول باستخدام Remote DOM — تكون شيفرتك في صندوق عزل لكنها تُعرَض أصيلًا داخل الصفحة، وليس ضمن iframe.
|
||||
|
||||
## أين يمكن استخدام مكوّنات الواجهة الأمامية
|
||||
|
||||
يمكن عرض مكوّنات الواجهة الأمامية في موقعين داخل Twenty:
|
||||
|
||||
* **اللوحة الجانبية** — المكوّنات غير عديمة الرأس تفتح في اللوحة الجانبية اليمنى. هذا هو السلوك الافتراضي عندما يتم تشغيل مكوّن واجهة أمامية من قائمة الأوامر.
|
||||
* **الويدجت (لوحات المعلومات وصفحات السجلات)** — يمكن تضمين مكوّنات الواجهة الأمامية كويدجت داخل تخطيطات الصفحات. عند تكوين لوحة معلومات أو تخطيط صفحة سجل، يمكن للمستخدمين إضافة ويدجت لمكوّن واجهة أمامية.
|
||||
|
||||
## مثال أساسي
|
||||
|
||||
أسرع طريقة لرؤية مكوّن أمامي قيد العمل هي تسجيله كأمر. إضافة حقل `command` مع `isPinned: true` يجعلُه يظهر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة — دون الحاجة إلى تخطيط صفحة:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
|
||||
const HelloWorld = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello from my app!</h1>
|
||||
<p>This component renders inside Twenty.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
|
||||
name: 'hello-world',
|
||||
description: 'A simple front component',
|
||||
component: HelloWorld,
|
||||
command: {
|
||||
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
|
||||
shortLabel: 'Hello',
|
||||
label: 'Hello World',
|
||||
icon: 'IconBolt',
|
||||
isPinned: true,
|
||||
availabilityType: 'GLOBAL',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
بعد المزامنة باستخدام `yarn twenty dev` (أو تشغيل الأمر لمرة واحدة `yarn twenty dev --once`)، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة:
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="زر إجراء سريع في الزاوية العلوية اليمنى" />
|
||||
</div>
|
||||
|
||||
انقره لعرض المكوّن مضمنًا داخل الصفحة.
|
||||
|
||||
## حقول التكوين
|
||||
|
||||
| الحقل | مطلوب | الوصف |
|
||||
| --------------------- | ----- | ----------------------------------------------------------------- |
|
||||
| `universalIdentifier` | نعم | معرّف فريد ثابت لهذا المكوّن |
|
||||
| `component` | نعم | دالة مكوّن React |
|
||||
| `name` | لا | اسم العرض |
|
||||
| `الوصف` | لا | وصف لما يفعله المكوّن |
|
||||
| `isHeadless` | لا | عيِّنه إلى `true` إذا كان المكوّن بلا واجهة مرئية (انظر أدناه) |
|
||||
| `أمر` | لا | سجّل المكوّن كأمر (انظر [خيارات الأوامر](#command-options) أدناه) |
|
||||
|
||||
## وضع مكوّن أمامي على صفحة
|
||||
|
||||
إضافةً إلى الأوامر، يمكنك تضمين مكوّن أمامي مباشرةً في صفحة سجل عبر إضافته كودجت في **تخطيط صفحة**. راجع قسم [definePageLayout](/l/ar/developers/extend/apps/skills-and-agents#definepagelayout) للتفاصيل.
|
||||
|
||||
## عديم الرأس مقابل غير عديم الرأس
|
||||
|
||||
تأتي مكوّنات الواجهة الأمامية بوضعَي عرض يتحكّم بهما الخيار `isHeadless`:
|
||||
|
||||
**غير عديم الرأس (افتراضي)** — يعرض المكوّن واجهة مستخدم مرئية. عند تشغيله من قائمة الأوامر يفتح في اللوحة الجانبية. هذا هو السلوك الافتراضي عندما تكون `isHeadless` تساوي `false` أو يتم تجاهلها.
|
||||
|
||||
**عديم الرأس (`isHeadless: true`)** — يتم تركيب المكوّن بشكل غير مرئي في الخلفية. لا يفتح اللوحة الجانبية. تم تصميم المكوّنات عديمة الرأس لإجراءات تنفّذ منطقًا ثم تُزيل تركيبها ذاتيًا — على سبيل المثال، تشغيل مهمة غير متزامنة، أو الانتقال إلى صفحة، أو إظهار نافذة تأكيد منبثقة. تتوافق بشكل طبيعي مع مكوّنات Command في SDK الموصوفة أدناه.
|
||||
|
||||
```tsx src/front-components/sync-tracker.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const SyncTracker = () => {
|
||||
const recordId = useRecordId();
|
||||
|
||||
useEffect(() => {
|
||||
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
|
||||
}, [recordId]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'sync-tracker',
|
||||
description: 'Tracks record views silently',
|
||||
isHeadless: true,
|
||||
component: SyncTracker,
|
||||
});
|
||||
```
|
||||
|
||||
نظرًا لأن المكوّن يُرجع `null`، فإن Twenty يتخطّى عرض حاوية له — ولن تظهر مساحة فارغة في التخطيط. لا يزال لدى المكوّن إمكانية الوصول إلى جميع الخطافات وواجهة برمجة الاتصال مع المضيف.
|
||||
|
||||
## مكوّنات Command في SDK
|
||||
|
||||
توفر حزمة `twenty-sdk` أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء.
|
||||
|
||||
استوردها من `twenty-sdk/command`:
|
||||
|
||||
* **`Command`** — يشغّل رد نداء غير متزامن عبر الخاصية `execute`.
|
||||
* **`CommandLink`** — ينتقل إلى مسار في التطبيق. الخصائص: `to`، `params`، `queryParams`، `options`.
|
||||
* **`CommandModal`** — يفتح نافذة تأكيد منبثقة. إذا أكّد المستخدم، ينفّذ رد النداء `execute`. الخصائص: `title`، `subtitle`، `execute`، `confirmButtonText`، `confirmButtonAccent`.
|
||||
* **`CommandOpenSidePanelPage`** — يفتح صفحة محدّدة في اللوحة الجانبية. الخصائص: `page`، `pageTitle`، `pageIcon`.
|
||||
|
||||
فيما يلي مثال كامل لمكوّن واجهة أمامية عديم الرأس يستخدم `Command` لتشغيل إجراء من قائمة الأوامر:
|
||||
|
||||
```tsx src/front-components/run-action.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { Command } from 'twenty-sdk/command';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
const RunAction = () => {
|
||||
const execute = async () => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
await client.mutation({
|
||||
createTask: {
|
||||
__args: { data: { title: 'Created by my app' } },
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return <Command execute={execute} />;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
|
||||
name: 'run-action',
|
||||
description: 'Creates a task from the command menu',
|
||||
component: RunAction,
|
||||
isHeadless: true,
|
||||
command: {
|
||||
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
|
||||
label: 'Run my action',
|
||||
icon: 'IconPlayerPlay',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
ومثال يستخدم `CommandModal` لطلب التأكيد قبل التنفيذ:
|
||||
|
||||
```tsx src/front-components/delete-draft.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { CommandModal } from 'twenty-sdk/command';
|
||||
|
||||
const DeleteDraft = () => {
|
||||
const execute = async () => {
|
||||
// perform the deletion
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandModal
|
||||
title="Delete draft?"
|
||||
subtitle="This action cannot be undone."
|
||||
execute={execute}
|
||||
confirmButtonText="Delete"
|
||||
confirmButtonAccent="danger"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
|
||||
name: 'delete-draft',
|
||||
description: 'Deletes a draft with confirmation',
|
||||
component: DeleteDraft,
|
||||
isHeadless: true,
|
||||
command: {
|
||||
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
|
||||
label: 'Delete draft',
|
||||
icon: 'IconTrash',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## الوصول إلى سياق وقت التشغيل
|
||||
|
||||
داخل مكوّنك، استخدم خطافات SDK للوصول إلى المستخدم الحالي، والسجل، ومثيل المكوّن:
|
||||
|
||||
```tsx src/front-components/record-info.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
useUserId,
|
||||
useRecordId,
|
||||
useFrontComponentId,
|
||||
} from 'twenty-sdk/front-component';
|
||||
|
||||
const RecordInfo = () => {
|
||||
const userId = useUserId();
|
||||
const recordId = useRecordId();
|
||||
const componentId = useFrontComponentId();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>User: {userId}</p>
|
||||
<p>Record: {recordId ?? 'No record context'}</p>
|
||||
<p>Component: {componentId}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
|
||||
name: 'record-info',
|
||||
component: RecordInfo,
|
||||
});
|
||||
```
|
||||
|
||||
الخطافات المتاحة:
|
||||
|
||||
| الخطّاف | القيم المعادة | الوصف |
|
||||
| --------------------------------------------- | ------------------ | ---------------------------------------------- |
|
||||
| `useUserId()` | `string` أو `null` | معرّف المستخدم الحالي |
|
||||
| `useRecordId()` | `string` أو `null` | معرّف السجل الحالي (عند وضعه على صفحة سجل) |
|
||||
| `useFrontComponentId()` | `string` | معرّف مثيل هذا المكوّن |
|
||||
| `useFrontComponentExecutionContext(selector)` | يختلف | الوصول إلى سياق التنفيذ الكامل عبر دالة محدِّد |
|
||||
|
||||
## واجهة الاتصال مع المضيف
|
||||
|
||||
يمكن للمكوّنات الأمامية تشغيل التنقّل والنوافذ المنبثقة والإشعارات باستخدام دوال من `twenty-sdk`:
|
||||
|
||||
| دالة | الوصف |
|
||||
| ----------------------------------------------- | ------------------------------ |
|
||||
| `navigate(to, params?, queryParams?, options?)` | الانتقال إلى صفحة داخل التطبيق |
|
||||
| `openSidePanelPage(params)` | فتح لوحة جانبية |
|
||||
| `closeSidePanel()` | إغلاق اللوحة الجانبية |
|
||||
| `openCommandConfirmationModal(params)` | عرض مربع حوار تأكيد |
|
||||
| `enqueueSnackbar(params)` | عرض إشعار توست |
|
||||
| `unmountFrontComponent()` | إلغاء تركيب المكوّن |
|
||||
| `updateProgress(progress)` | تحديث مؤشّر التقدّم |
|
||||
|
||||
فيما يلي مثال يستخدم واجهة برمجة تطبيقات المضيف لعرض Snackbar وإغلاق اللوحة الجانبية بعد اكتمال الإجراء:
|
||||
|
||||
```tsx src/front-components/archive-record.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { useRecordId } from 'twenty-sdk/front-component';
|
||||
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
const ArchiveRecord = () => {
|
||||
const recordId = useRecordId();
|
||||
|
||||
const handleArchive = async () => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
await client.mutation({
|
||||
updateTask: {
|
||||
__args: { id: recordId, data: { status: 'ARCHIVED' } },
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
await enqueueSnackbar({
|
||||
message: 'Record archived',
|
||||
variant: 'success',
|
||||
});
|
||||
|
||||
await closeSidePanel();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<p>Archive this record?</p>
|
||||
<button onClick={handleArchive}>Archive</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
|
||||
name: 'archive-record',
|
||||
description: 'Archives the current record',
|
||||
component: ArchiveRecord,
|
||||
});
|
||||
```
|
||||
|
||||
## خيارات الأوامر
|
||||
|
||||
إضافة حقل `command` إلى `defineFrontComponent` تُسجِّل المكوّن في قائمة الأوامر (Cmd+K). إذا كانت قيمة `isPinned` هي `true`، فسيظهر أيضًا كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة.
|
||||
|
||||
| الحقل | مطلوب | الوصف |
|
||||
| --------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `universalIdentifier` | نعم | معرّف فريد ثابت للأمر |
|
||||
| `التسمية` | نعم | التسمية الكاملة المعروضة في قائمة الأوامر (Cmd+K) |
|
||||
| `shortLabel` | لا | تسمية أقصر تُعرَض على زر الإجراء السريع المثبّت |
|
||||
| `أيقونة` | لا | اسم الأيقونة المعروض بجانب التسمية (مثل `'IconBolt'` و`'IconSend'`) |
|
||||
| `isPinned` | لا | عند كونها `true`، يعرض الأمر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة |
|
||||
| `availabilityType` | لا | تتحكّم في مكان ظهور الأمر: `'GLOBAL'` (متاح دائمًا)، و`'RECORD_SELECTION'` (فقط عند تحديد سجلات)، أو `'FALLBACK'` (يُعرَض عند عدم تطابق أي أوامر أخرى) |
|
||||
| `availabilityObjectUniversalIdentifier` | لا | تقييد الأمر بصفحات نوع كائن معيّن (مثل سجلات Company فقط) |
|
||||
| `conditionalAvailabilityExpression` | لا | تعبير منطقي للتحكم ديناميكيًا في ما إذا كان الأمر مرئيًا (انظر أدناه) |
|
||||
|
||||
## تعابير الإتاحة الشرطية
|
||||
|
||||
يتيح لك الحقل `conditionalAvailabilityExpression` التحكّم في وقت ظهور الأمر بناءً على سياق الصفحة الحالي. استورد متغيّرات ومشغّلات مضبوطة الأنواع من `twenty-sdk` لبناء التعابير:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
pageType,
|
||||
numberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
everyEquals,
|
||||
isDefined,
|
||||
} from 'twenty-sdk/front-component';
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'bulk-action',
|
||||
component: BulkAction,
|
||||
command: {
|
||||
universalIdentifier: '...',
|
||||
label: 'Bulk Update',
|
||||
availabilityType: 'RECORD_SELECTION',
|
||||
conditionalAvailabilityExpression: everyEquals(
|
||||
objectPermissions,
|
||||
'canUpdateObjectRecords',
|
||||
true,
|
||||
),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**متغيّرات السياق** — تُمثّل الحالة الحالية للصفحة:
|
||||
|
||||
| المتغيّر | النوع | الوصف |
|
||||
| ------------------------------ | ------------- | --------------------------------------------------------------- |
|
||||
| `pageType` | `string` | نوع الصفحة الحالي (مثل `'RecordIndexPage'` و`'RecordShowPage'`) |
|
||||
| `isInSidePanel` | `قيمة منطقية` | ما إذا كان المكوّن معروضًا في لوحة جانبية |
|
||||
| `numberOfSelectedRecords` | `رقم` | عدد السجلات المحدّدة حاليًا |
|
||||
| `isSelectAll` | `قيمة منطقية` | ما إذا كان "تحديد الكل" مفعّلًا |
|
||||
| `selectedRecords` | `array` | كائنات السجلات المحدّدة |
|
||||
| `favoriteRecordIds` | `array` | معرّفات السجلات المفضّلة |
|
||||
| `objectPermissions` | `الكائن` | الأذونات الخاصة بنوع الكائن الحالي |
|
||||
| `targetObjectReadPermissions` | `الكائن` | أذونات القراءة للكائن الهدف |
|
||||
| `targetObjectWritePermissions` | `الكائن` | أذونات الكتابة للكائن الهدف |
|
||||
| `featureFlags` | `الكائن` | أعلام الميزات المفعَّلة |
|
||||
| `objectMetadataItem` | `الكائن` | بيانات التعريف لنوع الكائن الحالي |
|
||||
| `hasAnySoftDeleteFilterOnView` | `قيمة منطقية` | ما إذا كان العرض الحالي يحتوي على مرشّح حذف منطقي |
|
||||
|
||||
**المُشغِّلات** — جمّع المتغيّرات في تعابير منطقية:
|
||||
|
||||
| المُشغِّل | الوصف |
|
||||
| ----------------------------------- | -------------------------------------------------------------- |
|
||||
| `isDefined(value)` | `true` إذا لم تكن القيمة null/undefined |
|
||||
| `isNonEmptyString(value)` | `true` إذا كانت القيمة سلسلة غير فارغة |
|
||||
| `includes(array, value)` | `true` إذا كانت المصفوفة تحتوي على القيمة |
|
||||
| `includesEvery(array, prop, value)` | `true` إذا كانت خاصية كل عنصر تتضمن القيمة |
|
||||
| `every(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في كل عنصر |
|
||||
| `everyDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في كل عنصر |
|
||||
| `everyEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في كل عنصر |
|
||||
| `some(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في عنصر واحد على الأقل |
|
||||
| `someDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في عنصر واحد على الأقل |
|
||||
| `someEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في عنصر واحد على الأقل |
|
||||
| `someNonEmptyString(array, prop)` | `true` إذا كانت الخاصية سلسلة غير فارغة في عنصر واحد على الأقل |
|
||||
| `none(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بخطأ في كل عنصر |
|
||||
| `noneDefined(array, prop)` | `true` إذا كانت الخاصية غير معرّفة في كل عنصر |
|
||||
| `noneEquals(array, prop, value)` | `true` إذا لم تكن الخاصية تساوي القيمة في أي عنصر |
|
||||
|
||||
## الأصول العامة
|
||||
|
||||
يمكن للمكوّنات الأمامية الوصول إلى ملفات من دليل `public/` للتطبيق باستخدام `getPublicAssetUrl`:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'logo',
|
||||
component: Logo,
|
||||
});
|
||||
```
|
||||
|
||||
راجع [قسم الأصول العامة](/l/ar/developers/extend/apps/cli-and-testing#public-assets-public-folder) للتفاصيل.
|
||||
|
||||
## التنسيق
|
||||
|
||||
تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام:
|
||||
|
||||
* **أنماط مضمنة** — `style={{ color: 'red' }}`
|
||||
* **مكوّنات Twenty لواجهة المستخدم** — استورد من `twenty-sdk/ui` (Button وTag وStatus وChip وAvatar وغيرها)
|
||||
* **Emotion** — CSS-in-JS مع `@emotion/react`
|
||||
* **Styled-components** — أنماط `styled.div`
|
||||
* **Tailwind CSS** — أصناف مساعدة
|
||||
* **أي مكتبة CSS-in-JS** متوافقة مع React
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { Button, Tag, Status } from 'twenty-sdk/ui';
|
||||
|
||||
const StyledWidget = () => {
|
||||
return (
|
||||
<div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
|
||||
<Button title="Click me" onClick={() => alert('Clicked!')} />
|
||||
<Tag text="Active" color="green" />
|
||||
<Status color="green" text="Online" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
|
||||
name: 'styled-widget',
|
||||
component: StyledWidget,
|
||||
});
|
||||
```
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: البدء
|
||||
icon: rocket
|
||||
description: أنشئ أول تطبيق Twenty خلال دقائق.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
</Warning>
|
||||
|
||||
## ما هي التطبيقات؟
|
||||
|
||||
تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول مخصّصة ووظائف منطقية ومكوّنات الواجهة الأمامية ومهارات الذكاء الاصطناعي وغير ذلك — جميعها تُدار ككود. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تعرّف نموذج بياناتك ومنطقك في TypeScript وتقوم بنشره إلى مساحة عمل واحدة أو أكثر.
|
||||
|
|
|
|||
131
packages/twenty-docs/l/ar/developers/extend/apps/layout.mdx
Normal file
131
packages/twenty-docs/l/ar/developers/extend/apps/layout.mdx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
title: التخطيط
|
||||
description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty.
|
||||
icon: table-columns
|
||||
---
|
||||
|
||||
Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged.
|
||||
|
||||
## Layout concepts
|
||||
|
||||
| Concept | What it controls | كيان |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- |
|
||||
| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` |
|
||||
| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` |
|
||||
| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` |
|
||||
|
||||
Views, navigation items, and page layouts reference each other by `universalIdentifier`:
|
||||
|
||||
* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view.
|
||||
* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/ar/developers/extend/apps/front-components) inside its tabs as widgets.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineView" description="تعريف العروض المحفوظة للكائنات">
|
||||
|
||||
العروض هي تكوينات محفوظة لكيفية عرض سجلات كائن ما — بما في ذلك الحقول المرئية وترتيبها وأي مرشّحات أو مجموعات مُطبَّقة. استخدم `defineView()` لتضمين عروض مُهيّأة مسبقًا مع تطبيقك:
|
||||
|
||||
```ts src/views/example-view.ts
|
||||
import { defineView, ViewKey } from 'twenty-sdk/define';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||||
import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'All example items',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconList',
|
||||
key: ViewKey.INDEX,
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
|
||||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا العرض.
|
||||
* `key` يحدّد نوع العرض (مثل `ViewKey.INDEX` لعرض القائمة الرئيسي).
|
||||
* `fields` يتحكّم في الأعمدة الظاهرة وترتيبها. يشير كل حقل إلى `fieldMetadataUniversalIdentifier`.
|
||||
* يمكنك أيضًا تعريف `filters` و`filterGroups` و`groups` و`fieldGroups` لمزيد من التكوينات المتقدمة.
|
||||
* `position` يتحكّم في الترتيب عند وجود عدة عروض لنفس الكائن.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineNavigationMenuItem" description="تعريف روابط التنقل في الشريط الجانبي">
|
||||
|
||||
تضيف عناصر قائمة التنقل إدخالات مخصّصة إلى الشريط الجانبي لمساحة العمل. استخدم `defineNavigationMenuItem()` للارتباط بالعروض أو عناوين URL خارجية أو الكائنات:
|
||||
|
||||
```ts src/navigation-menu-items/example-navigation-menu-item.ts
|
||||
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
|
||||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
color: 'blue',
|
||||
position: 0,
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* `type` يحدّد إلى ماذا يرتبط عنصر القائمة: `NavigationMenuItemType.VIEW` لعرض محفوظ، أو `NavigationMenuItemType.LINK` لعنوان URL خارجي.
|
||||
* لروابط العروض، عيِّن `viewUniversalIdentifier`. لروابط خارجية، عيِّن `link`.
|
||||
* `position` يتحكّم في الترتيب ضمن الشريط الجانبي.
|
||||
* `icon` و`color` (اختياريان) يخصّصان المظهر.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePageLayout" description="عرّف تخطيطات صفحات مخصّصة لعرض السجلات">
|
||||
|
||||
تتيح لك تخطيطات الصفحات تخصيص مظهر صفحة تفاصيل السجل — ما الألسنة التي تظهر، وما الويدجتات داخل كل لسان، وكيف يتم ترتيبها. استخدم `definePageLayout()` لتضمين تخطيطات مخصّصة مع تطبيقك:
|
||||
|
||||
```ts src/page-layouts/example-record-page-layout.ts
|
||||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||||
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
|
||||
|
||||
export default definePageLayout({
|
||||
universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
|
||||
name: 'Example Record Page',
|
||||
type: 'RECORD_PAGE',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
tabs: [
|
||||
{
|
||||
universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
|
||||
title: 'Hello World',
|
||||
position: 50,
|
||||
icon: 'IconWorld',
|
||||
layoutMode: PageLayoutTabLayoutMode.CANVAS,
|
||||
widgets: [
|
||||
{
|
||||
universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
|
||||
title: 'Hello World',
|
||||
type: 'FRONT_COMPONENT',
|
||||
configuration: {
|
||||
configurationType: 'FRONT_COMPONENT',
|
||||
frontComponentUniversalIdentifier:
|
||||
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* `type` يكون عادة `'RECORD_PAGE'` لتخصيص عرض التفاصيل لكائن محدّد.
|
||||
* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا التخطيط.
|
||||
* يُعرّف كل `tab` قسمًا من الصفحة مع `title` و`position` و`layoutMode` (`CANVAS` لتخطيط حرّ).
|
||||
* يمكن لكل `widget` داخل لسان أن يعرض مكوّنًا أماميًا أو قائمة علاقات أو أنواع ويدجت مدمجة أخرى.
|
||||
* `position` على الألسنة يتحكّم في ترتيبها. استخدم قيمًا أعلى (مثل 50) لوضع الألسنة المخصّصة بعد الألسنة المدمجة.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
|
@ -0,0 +1,559 @@
|
|||
---
|
||||
title: الوظائف المنطقية
|
||||
description: Define server-side TypeScript functions with HTTP, cron, and database event triggers.
|
||||
icon: bolt
|
||||
---
|
||||
|
||||
Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineLogicFunction" description="عرّف الدوال المنطقية ومشغّلاتها">
|
||||
|
||||
كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية.
|
||||
|
||||
```ts src/logic-functions/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define';
|
||||
import { CoreApiClient, type Person } from 'twenty-client-sdk/core';
|
||||
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new CoreApiClient();
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
: 'Hello world';
|
||||
|
||||
const result = await client.mutation({
|
||||
createPostCard: {
|
||||
__args: { data: { name } },
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/post-card/create',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
/*databaseEventTriggerSettings: {
|
||||
eventName: 'people.created',
|
||||
},*/
|
||||
/*cronTriggerSettings: {
|
||||
pattern: '0 0 1 1 *',
|
||||
},*/
|
||||
});
|
||||
```
|
||||
|
||||
أنواع المشغّلات المتاحة:
|
||||
* **httpRoute**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**:
|
||||
> مثال: `path: '/post-card/create'` يمكن استدعاؤه عبر `https://your-twenty-server.com/s/post-card/create`
|
||||
* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON.
|
||||
* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة.
|
||||
> مثال: `person.updated`، `*.created`، `company.*`
|
||||
|
||||
<Note>
|
||||
يمكنك أيضًا تنفيذ دالة يدويًا باستخدام CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
|
||||
```
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
يمكنك متابعة السجلات باستخدام:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty logs
|
||||
```
|
||||
</Note>
|
||||
|
||||
#### حمولة مشغل المسار
|
||||
|
||||
عندما يستدعي مُشغِّل المسار وظيفتك المنطقية، فإنها تتلقّى كائن `RoutePayload` الذي يتبع [صيغة AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html).
|
||||
استورد نوع `RoutePayload` من `twenty-sdk`:
|
||||
|
||||
```ts
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const { headers, queryStringParameters, pathParameters, body } = event;
|
||||
const { method, path } = event.requestContext.http;
|
||||
|
||||
return { message: 'Success' };
|
||||
};
|
||||
```
|
||||
|
||||
يحتوي نوع `RoutePayload` على البنية التالية:
|
||||
|
||||
| الخاصية | النوع | الوصف | مثال |
|
||||
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||
| `headers` | `Record\<string, string \| undefined>` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) | انظر القسم أدناه |
|
||||
| `queryStringParameters` | `Record\<string, string \| undefined>` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
|
||||
| `pathParameters` | `Record\<string, string \| undefined>` | معلمات المسار المستخرجة من نمط المسار | `/users/:id`, `/users/123` -> `{ id: '123' }` |
|
||||
| `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
|
||||
| `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | |
|
||||
| `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | |
|
||||
| `requestContext.http.path` | `string` | المسار الخام للطلب | |
|
||||
|
||||
|
||||
#### forwardedRequestHeaders
|
||||
|
||||
افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية.
|
||||
للوصول إلى رؤوس محددة، أدرِجها في مصفوفة `forwardedRequestHeaders`:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/webhook',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: false,
|
||||
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
في معالجك، يمكنك الوصول إلى الرؤوس المُمرَّرة بهذه الطريقة:
|
||||
|
||||
```ts
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const signature = event.headers['x-webhook-signature'];
|
||||
const contentType = event.headers['content-type'];
|
||||
|
||||
// Validate webhook signature...
|
||||
return { received: true };
|
||||
};
|
||||
```
|
||||
|
||||
<Note>
|
||||
تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`).
|
||||
</Note>
|
||||
|
||||
#### إتاحة دالة كأداة
|
||||
|
||||
يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عند تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي في Twenty ويمكن استخدامها في أتمتة سير العمل.
|
||||
|
||||
لتمييز دالة منطقية كأداة، عيِّن `isTool: true`:
|
||||
|
||||
```ts src/logic-functions/enrich-company.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
const handler = async (params: { companyName: string; domain?: string }) => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
const result = await client.mutation({
|
||||
createTask: {
|
||||
__args: {
|
||||
data: {
|
||||
title: `Enrich data for ${params.companyName}`,
|
||||
body: `Domain: ${params.domain ?? 'unknown'}`,
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { taskId: result.createTask.id };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
||||
name: 'enrich-company',
|
||||
description: 'Enrich a company record with external data',
|
||||
timeoutSeconds: 10,
|
||||
handler,
|
||||
isTool: true,
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة الأحداث في الوقت نفسه.
|
||||
* **`toolInputSchema`** (اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يُحسَب المخطط تلقائيًا من خلال تحليل ساكن للشيفرة المصدرية، ولكن يمكنك تعيينه صراحةً:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
...,
|
||||
toolInputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
companyName: {
|
||||
type: 'string',
|
||||
description: 'The name of the company to enrich',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
description: 'The company website domain (optional)',
|
||||
},
|
||||
},
|
||||
required: ['companyName'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePostInstallLogicFunction" description="تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق)">
|
||||
|
||||
دالة ما بعد التثبيت هي دالة منطقية تعمل تلقائيًا بعد تثبيت تطبيقك على مساحة عمل. ينفّذه الخادم **بعد** مزامنة البيانات الوصفية للتطبيق وإنشاء عميل SDK، بحيث تكون مساحة العمل جاهزة تمامًا للاستخدام ويكون المخطط الجديد مطبَّقًا. تشمل حالات الاستخدام النموذجية تهيئة البيانات الافتراضية، وإنشاء السجلات الأولية، وتكوين إعدادات مساحة العمل، أو توفير الموارد على خدمات جهات خارجية.
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (payload: InstallPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: false,
|
||||
shouldRunSynchronously: false,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
|
||||
* يتلقى المعالج `InstallPayload` يحتوي على `{ previousVersion?: string; newVersion: string }` — حيث إن `newVersion` هو الإصدار الجاري تثبيته، و`previousVersion` هو الإصدار الذي كان مُثبّتًا سابقًا (أو `undefined` عند التثبيت الأولي). استخدم هذه القيم للتمييز بين عمليات التثبيت الجديدة والترقيات ولتشغيل منطق الترحيل الخاص بالإصدار.
|
||||
* **موعد تشغيل الخطاف**: في عمليات التثبيت الجديدة فقط، افتراضيًا. مرّر `shouldRunOnVersionUpgrade: true` إذا كنت تريد تشغيله أيضًا عند ترقية التطبيق من إصدار سابق. عند إغفاله، تكون القيمة الافتراضية للعلم `false`، وتتجاوز الترقيات هذا الخطاف.
|
||||
* **نموذج التنفيذ — غير متزامن افتراضيًا، والتزامني اختياري**: يتحكّم العلم `shouldRunSynchronously` في كيفية تنفيذ ما بعد التثبيت.
|
||||
* `shouldRunSynchronously: false` *(الإعداد الافتراضي)* — يتم **إدراج الخطاف في قائمة الرسائل** مع `retryLimit: 3` ويعمل بشكل غير متزامن داخل عامل عمل. يعود ردّ التثبيت بمجرد وضع المهمة في الطابور، لذا فإن معالجًا بطيئًا أو متعطلًا لا يحجب المستدعي. سيُجرِّب العامل إعادة المحاولة حتى ثلاث مرات. **استخدم هذا للمهام طويلة التشغيل** — بَذر مجموعات بيانات كبيرة، استدعاء واجهات برمجة تطبيقات خارجية بطيئة، تهيئة موارد خارجية، أو أي شيء قد يتجاوز نافذة استجابة HTTP المعقولة.
|
||||
* `shouldRunSynchronously: true` — يُنفّذ الخطاف **ضمن تدفّق التثبيت مباشرةً** (نفس المنفِّذ كما قبل التثبيت). يَحجُب طلب التثبيت حتى ينتهي المعالج، وإذا رمى استثناءً، سيتلقى مستدعي التثبيت `POST_INSTALL_ERROR`. لا توجد محاولات إعادة تلقائية. **استخدم هذا للمهام السريعة التي يجب إكمالها قبل الاستجابة** — مثل إظهار خطأ تحقق للمستخدم، أو إعداد سريع سيعتمد عليه العميل مباشرةً بعد عودة نداء التثبيت. ضع في اعتبارك أن ترحيل البيانات الوصفية يكون قد طُبِّق بالفعل عند تشغيل ما بعد التثبيت، لذلك فإن فشل الوضع المتزامن **لا** يعيد التغييرات على المخطط إلى الوراء — بل يكتفي بإبراز الخطأ.
|
||||
* تأكّد من أن معالجك قابل للتنفيذ المتكرر دون آثار جانبية. في الوضع غير المتزامن قد تُعيد قائمة الانتظار المحاولة حتى ثلاث مرات؛ وفي أي من الوضعين قد يعمل الخطاف مجددًا أثناء الترقيات عند ضبط `shouldRunOnVersionUpgrade: true`.
|
||||
* متغيرات البيئة `APPLICATION_ID` و`APP_ACCESS_TOKEN` و`API_URL` متاحة داخل المعالج (كما في أي دالة منطق أخرى)، لذا يمكنك استدعاء واجهة Twenty API باستخدام رمز وصول للتطبيق مقيّد بنطاق تطبيقك.
|
||||
* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
|
||||
* تُرفَق خصائص الدالة `universalIdentifier` و`shouldRunOnVersionUpgrade` و`shouldRunSynchronously` تلقائيًا ببيان التطبيق ضمن الحقل `postInstallLogicFunction` أثناء عملية البناء — ولا تحتاج إلى الإشارة إليها في `defineApplication()`.
|
||||
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
|
||||
* **لا يُنفَّذ في وضع التطوير**: عند تسجيل تطبيق محليًا (عبر `yarn twenty dev`)، يتجاوز الخادم تدفّق التثبيت بالكامل ويُزامن الملفات مباشرةً عبر مراقِب CLI — لذا لن يعمل ما بعد التثبيت في وضع التطوير مطلقًا، بغضّ النظر عن `shouldRunSynchronously`. استخدم `yarn twenty exec --postInstall` لتشغيله يدويًا على مساحة عمل قيد التشغيل.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePreInstallLogicFunction" description="تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق)">
|
||||
|
||||
دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا أثناء التثبيت، **قبل تطبيق ترحيل البيانات الوصفية لمساحة العمل**. تتشارك نفس بنية الحمولة مع ما بعد التثبيت (`InstallPayload`)، لكنها موضوعة أبكر في تدفّق التثبيت كي تجهّز حالة يعتمد عليها الترحيل القادم — ومن الاستخدامات الشائعة: نسخ البيانات احتياطيًا، التحقق من التوافق مع المخطط الجديد، أو أرشفة السجلات التي ستُعاد هيكلتها أو ستُحذف.
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (payload: InstallPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: true,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --preInstall
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — نفس الإعدادات المتخصصة كما في ما بعد التثبيت، لكنها مرتبطة بموضع مختلف ضمن دورة الحياة.
|
||||
* يتلقّى كلٌّ من معالجي ما قبل التثبيت وما بعد التثبيت النوع نفسه `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. استورده مرة واحدة وأعد استخدامه لكلا الخطافين.
|
||||
* **موعد تشغيل الخطاف**: موضوع مباشرةً قبل ترحيل البيانات الوصفية لمساحة العمل (`synchronizeFromManifest`). قبل التنفيذ، يُشغِّل الخادم مزامنة "pared-down sync" ذات طابع إضافي فقط تقوم بتسجيل دالة ما قبل التثبيت للإصدار **الجديد** في البيانات الوصفية لمساحة العمل — دون لمس أي شيء آخر — ثم يُنفّذها. لأن هذه المزامنة «إضافية فقط»، تبقى كائنات وحقول وبيانات الإصدار السابق سليمة عند تشغيل معالجك: يمكنك قراءة حالة ما قبل الترحيل ونسخها احتياطيًا بأمان.
|
||||
* **نموذج التنفيذ**: يُنفَّذ ما قبل التثبيت **بشكل متزامن** و**يحجب عملية التثبيت**. إذا رمى المعالج استثناءً، تُلغى عملية التثبيت قبل تطبيق أي تغييرات على المخطط — وتبقى مساحة العمل على الإصدار السابق بحالة متّسقة. هذا مقصود: ما قبل التثبيت هو فرصتك الأخيرة لرفض ترقية تنطوي على مخاطر.
|
||||
* كما هو الحال مع ما بعد التثبيت، يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. تُربَط تلقائيًا ببيان التطبيق تحت `preInstallLogicFunction` أثناء عملية البناء.
|
||||
* **لا يُنفَّذ في وضع التطوير**: كما في ما بعد التثبيت — يتم تجاوز تدفّق التثبيت بالكامل للتطبيقات المسجّلة محليًا، لذا لن يعمل ما قبل التثبيت مطلقًا عند `yarn twenty dev`. استخدم `yarn twenty exec --preInstall` لتشغيله يدويًا.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="ما قبل التثبيت مقابل ما بعد التثبيت: متى تستخدم أيّهما" description="اختيار خطاف التثبيت المناسب">
|
||||
|
||||
كلا الخطافين جزء من تدفّق التثبيت نفسه ويتلقّيان نفس `InstallPayload`. الاختلاف يكمن في **موعد** تشغيلهما نسبةً إلى ترحيل البيانات الوصفية لمساحة العمل، وهذا يغيّر البيانات التي يمكنهما التعامل معها بأمان.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ install flow │
|
||||
│ │
|
||||
│ upload package → [pre-install] → metadata migration → │
|
||||
│ generate SDK → [post-install] │
|
||||
│ │
|
||||
│ old schema visible new schema visible │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
ما قبل التثبيت دائمًا **متزامن** (يحجب التثبيت ويمكنه إحباطه). ما بعد التثبيت **غير متزامن افتراضيًا** — يُدرج على عامل مع محاولات إعادة تلقائية — لكن يمكن التبديل إلى تنفيذ متزامن عبر `shouldRunSynchronously: true`. راجع الأكورديون `definePostInstallLogicFunction` أعلاه لمعرفة متى تستخدم كل وضع.
|
||||
|
||||
**استخدم `post-install` لأي شيء يتطلّب وجود المخطط الجديد.** وهذا هو السيناريو الشائع:
|
||||
|
||||
* بَذر بيانات افتراضية (إنشاء سجلات أولية وعروض افتراضية ومحتوى تجريبي) للكائنات والحقول المضافة حديثًا.
|
||||
* تسجيل خطافات الويب مع خدمات أطراف ثالثة بعد أن حصل التطبيق على بيانات الاعتماد الخاصة به.
|
||||
* استدعاء واجهة برمجة التطبيقات الخاصة بك لإكمال إعداد يعتمد على البيانات الوصفية المتزامنة.
|
||||
* منطق idempotent لتحقيق "تأكّد من وجود هذا" والذي ينبغي مواءمة الحالة في كل ترقية — بالاقتران مع `shouldRunOnVersionUpgrade: true`.
|
||||
|
||||
مثال — بَذر سجل `PostCard` افتراضي بعد التثبيت:
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
import { createClient } from './generated/client';
|
||||
|
||||
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
|
||||
if (previousVersion) return; // fresh installs only
|
||||
|
||||
const client = createClient();
|
||||
await client.postCard.create({
|
||||
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
|
||||
});
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||||
name: 'post-install',
|
||||
description: 'Seeds a welcome post card after install.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: false,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**استخدم `pre-install` عندما قد يُتلف الترحيل أو يدمّر البيانات الحالية.** لأن ما قبل التثبيت يعمل مقابل المخطط *السابق* وفشله يُرجِع الترقية إلى الوراء، فهو المكان المناسب لأي شيء محفوف بالمخاطر:
|
||||
|
||||
* **نسخ البيانات احتياطيًا قبل حذفها أو إعادة هيكلتها** — مثل إزالة حقل في v2 وتحتاج إلى نسخ قيمه إلى حقل آخر أو تصديرها إلى التخزين قبل تشغيل الترحيل.
|
||||
* **أرشفة السجلات التي سيبطلها قيد جديد** — مثل أن يصبح حقل ما `NOT NULL` وتحتاج أولًا إلى حذف الصفوف ذات القيم الفارغة أو إصلاحها.
|
||||
* **التحقق من التوافق ورفض الترقية إذا تعذّر ترحيل البيانات الحالية بسلاسة** — ارمِ من داخل المعالج وسيُلغى التثبيت دون تطبيق أي تغييرات. هذا أكثر أمانًا من اكتشاف عدم التوافق في منتصف الترحيل.
|
||||
* **إعادة تسمية البيانات أو إعادة تعيين مفاتيحها** قبل تغيير في المخطط قد يؤدي إلى فقدان الارتباط.
|
||||
|
||||
مثال — أرشف السجلات قبل ترحيل هدّام:
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
import { createClient } from './generated/client';
|
||||
|
||||
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
|
||||
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
|
||||
if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = createClient();
|
||||
const legacyRecords = await client.postCard.findMany({
|
||||
where: { notes: { isNotNull: true } },
|
||||
});
|
||||
|
||||
if (legacyRecords.length === 0) return;
|
||||
|
||||
// Copy legacy `notes` into the new `description` field before the migration
|
||||
// drops the `notes` column. If this fails, the upgrade is aborted and the
|
||||
// workspace stays on v1 with all data intact.
|
||||
await Promise.all(
|
||||
legacyRecords.map((record) =>
|
||||
client.postCard.update({
|
||||
where: { id: record.id },
|
||||
data: { description: record.notes },
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
|
||||
name: 'pre-install',
|
||||
description: 'Backs up legacy notes into description before the v2 migration.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: true,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**قاعدة عامة:**
|
||||
|
||||
| You want to... | استخدام |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
|
||||
| بذر بيانات افتراضية، تهيئة مساحة العمل، تسجيل موارد خارجية | `post-install` |
|
||||
| تشغيل بذر طويل الأمد أو استدعاءات أطراف ثالثة لا ينبغي أن تحجب استجابة التثبيت | `post-install` (الإعداد الافتراضي — `shouldRunSynchronously: false`، مع محاولات إعادة من العامل) |
|
||||
| تشغيل إعداد سريع سيعتمد عليه المستدعي مباشرةً بعد عودة نداء التثبيت | `post-install` مع `shouldRunSynchronously: true` |
|
||||
| قراءة البيانات أو نسخها احتياطيًا والتي قد يفقدها الترحيل القادم | `pre-install` |
|
||||
| رفض ترقية قد تُفسد البيانات الحالية | `pre-install` (ارمِ من المعالج) |
|
||||
| تنفيذ مواءمة في كل ترقية | `post-install` مع `shouldRunOnVersionUpgrade: true` |
|
||||
| تنفيذ إعداد لمرة واحدة في التثبيت الأول فقط | `post-install` مع `shouldRunOnVersionUpgrade: false` (الإعداد الافتراضي) |
|
||||
|
||||
<Note>
|
||||
إذا ساورك الشك، فاجعل الافتراضي هو **post-install**. الجأ إلى ما قبل التثبيت فقط عندما يكون الترحيل نفسه هدّامًا وتحتاج إلى التقاط الحالة السابقة قبل أن تزول.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## عملاء واجهة برمجة تطبيقات مضبوطة الأنواع (`twenty-client-sdk`)
|
||||
|
||||
توفر حزمة `twenty-client-sdk` عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية.
|
||||
|
||||
| العميل | استيراد | نقطة النهاية | مُولَّد؟ |
|
||||
| ------------------- | ---------------------------- | --------------------------------------------------- | -------------------------- |
|
||||
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — بيانات مساحة العمل (السجلات، الكائنات) | نعم، في وقت التطوير/البناء |
|
||||
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — تكوين مساحة العمل، رفع الملفات | لا، يأتي مُجهزًا مسبقًا |
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="CoreApiClient" description="استعلام وتعديل بيانات مساحة العمل (السجلات، الكائنات)">
|
||||
|
||||
`CoreApiClient` هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يُولَّد من مخطط مساحة العمل لديك أثناء `yarn twenty dev` أو `yarn twenty build`، لذا فهو مضبوط الأنواع بالكامل ليتوافق مع كائناتك وحقولك.
|
||||
|
||||
```ts
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
const client = new CoreApiClient();
|
||||
|
||||
// Query records
|
||||
const { companies } = await client.query({
|
||||
companies: {
|
||||
edges: {
|
||||
node: {
|
||||
id: true,
|
||||
name: true,
|
||||
domainName: {
|
||||
primaryLinkLabel: true,
|
||||
primaryLinkUrl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create a record
|
||||
const { createCompany } = await client.mutation({
|
||||
createCompany: {
|
||||
__args: {
|
||||
data: {
|
||||
name: 'Acme Corp',
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
يستخدم العميل صياغة مجموعة اختيار: مرِّر `true` لتضمين حقل، واستخدم `__args` للوسيطات، وعشّش الكائنات للعلاقات. ستحصل على إكمال تلقائي كامل وفحص للأنواع يعتمد على مخطط مساحة العمل لديك.
|
||||
|
||||
<Note>
|
||||
**يتم توليد CoreApiClient في وقت التطوير/البناء.** إذا استخدمته دون تشغيل `yarn twenty dev` أو `yarn twenty build` أولًا، فسيؤدي ذلك إلى خطأ. تحدث عملية التوليد تلقائيًا — إذ يستطلع CLI مخطط GraphQL لمساحة عملك وينشئ عميلًا مضبوط الأنواع باستخدام `@genql/cli`.
|
||||
</Note>
|
||||
|
||||
#### استخدام CoreSchema للتعليقات التوضيحية للأنواع
|
||||
|
||||
`CoreSchema` يوفّر أنواع TypeScript المطابقة لكائنات مساحة العمل لديك — مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال:
|
||||
|
||||
```ts
|
||||
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
|
||||
import { useState } from 'react';
|
||||
|
||||
const [company, setCompany] = useState<
|
||||
Pick<CoreSchema.Company, 'id' | 'name'> | undefined
|
||||
>(undefined);
|
||||
|
||||
const client = new CoreApiClient();
|
||||
const result = await client.query({
|
||||
company: {
|
||||
__args: { filter: { position: { eq: 1 } } },
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
setCompany(result.company);
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="MetadataApiClient" description="إعدادات مساحة العمل، والتطبيقات، ورفع الملفات">
|
||||
|
||||
يأتي `MetadataApiClient` مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية `/metadata` للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات.
|
||||
|
||||
```ts
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
// List first 10 objects in the workspace
|
||||
const { objects } = await metadataClient.query({
|
||||
objects: {
|
||||
edges: {
|
||||
node: {
|
||||
id: true,
|
||||
nameSingular: true,
|
||||
namePlural: true,
|
||||
labelSingular: true,
|
||||
isCustom: true,
|
||||
},
|
||||
},
|
||||
__args: {
|
||||
filter: {},
|
||||
paging: { first: 10 },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### رفع الملفات
|
||||
|
||||
يتضمن `MetadataApiClient` طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع الملف:
|
||||
|
||||
```ts
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const fileBuffer = fs.readFileSync('./invoice.pdf');
|
||||
|
||||
const uploadedFile = await metadataClient.uploadFile(
|
||||
fileBuffer, // file contents as a Buffer
|
||||
'invoice.pdf', // filename
|
||||
'application/pdf', // MIME type
|
||||
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier
|
||||
);
|
||||
|
||||
console.log(uploadedFile);
|
||||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||||
```
|
||||
|
||||
| المعلمة | النوع | الوصف |
|
||||
| ---------------------------------- | -------- | ---------------------------------------------------------------------- |
|
||||
| `fileBuffer` | `Buffer` | المحتوى الخام للملف |
|
||||
| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) |
|
||||
| `contentType` | `string` | نوع MIME (القيمة الافتراضية `application/octet-stream` إذا لم يُحدَّد) |
|
||||
| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك |
|
||||
|
||||
النقاط الرئيسية:
|
||||
* يستخدم `universalIdentifier` الخاص بالحقل (وليس معرّفه الخاص بمساحة العمل)، بحيث يعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك.
|
||||
* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية:
|
||||
|
||||
* `TWENTY_API_URL` — عنوان URL الأساسي لواجهة Twenty البرمجية
|
||||
* `TWENTY_APP_ACCESS_TOKEN` — مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك
|
||||
|
||||
لست **بحاجة** إلى تمرير هذه القيم إلى العملاء — فهي تُقرأ تلقائيًا من `process.env`. تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `defaultRoleUniversalIdentifier` ضمن `application-config.ts`.
|
||||
</Note>
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: النشر
|
||||
icon: رفع
|
||||
description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أو انشره داخليًا.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
</Warning>
|
||||
|
||||
## نظرة عامة
|
||||
|
||||
بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: المهارات والوكلاء
|
||||
description: Define AI skills and agents for your app.
|
||||
icon: robot
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Skills and agents are currently in alpha. الميزة تعمل لكنها لا تزال قيد التطور.
|
||||
</Warning>
|
||||
|
||||
Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineSkill" description="عرّف مهارات وكيل الذكاء الاصطناعي">
|
||||
|
||||
تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج:
|
||||
|
||||
```ts src/skills/example-skill.ts
|
||||
import { defineSkill } from 'twenty-sdk/define';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'sales-outreach',
|
||||
label: 'Sales Outreach',
|
||||
description: 'Guides the AI agent through a structured sales outreach process',
|
||||
icon: 'IconBrain',
|
||||
content: `You are a sales outreach assistant. When reaching out to a prospect:
|
||||
1. Research the company and recent news
|
||||
2. Identify the prospect's role and likely pain points
|
||||
3. Draft a personalized message referencing specific details
|
||||
4. Keep the tone professional but conversational`,
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case).
|
||||
* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم.
|
||||
* `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي.
|
||||
* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
|
||||
* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineAgent" description="عرِّف وكلاء الذكاء الاصطناعي باستخدام موجهات مخصّصة">
|
||||
|
||||
الوكلاء هم مساعدون ذكاء اصطناعي يعيشون داخل مساحة العمل لديك. استخدم `defineAgent()` لإنشاء وكلاء بموجه نظام مخصّص:
|
||||
|
||||
```ts src/agents/example-agent.ts
|
||||
import { defineAgent } from 'twenty-sdk/define';
|
||||
|
||||
export default defineAgent({
|
||||
universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
|
||||
name: 'sales-assistant',
|
||||
label: 'Sales Assistant',
|
||||
description: 'Helps the sales team draft outreach emails and research prospects',
|
||||
icon: 'IconRobot',
|
||||
prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.',
|
||||
});
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
* `name` هي سلسلة معرّف فريدة للوكيل (يُنصح باستخدام kebab-case).
|
||||
* `label` هو اسم العرض الظاهر في واجهة المستخدم.
|
||||
* `prompt` هو موجه النظام الذي يحدّد سلوك الوكيل.
|
||||
* `description` (اختياري) يوفّر سياقًا حول ما يفعله الوكيل.
|
||||
* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
|
||||
* `modelId` (اختياري) يتجاوز نموذج الذكاء الاصطناعي الافتراضي الذي يستخدمه الوكيل.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
189
packages/twenty-docs/l/ar/developers/extend/oauth.mdx
Normal file
189
packages/twenty-docs/l/ar/developers/extend/oauth.mdx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
title: OAuth
|
||||
icon: المفتاح
|
||||
description: Authorization code flow with PKCE and client credentials for server-to-server access.
|
||||
---
|
||||
|
||||
Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard.
|
||||
|
||||
## When to Use OAuth
|
||||
|
||||
| السيناريو | Auth Method |
|
||||
| --------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| Internal scripts, automation | [API Key](/l/ar/developers/extend/api#authentication) |
|
||||
| External app acting on behalf of a user | **OAuth — Authorization Code** |
|
||||
| Server-to-server, no user context | **OAuth — Client Credentials** |
|
||||
| Twenty App with UI extensions | [Apps](/l/ar/developers/extend/apps/getting-started) (OAuth is handled automatically) |
|
||||
|
||||
## Register a Client
|
||||
|
||||
Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically:
|
||||
|
||||
```bash
|
||||
POST /oauth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"client_name": "My Integration",
|
||||
"redirect_uris": ["https://myapp.com/callback"],
|
||||
"grant_types": ["authorization_code"],
|
||||
"token_endpoint_auth_method": "client_secret_post"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": "abc123",
|
||||
"client_secret": "secret456",
|
||||
"client_name": "My Integration",
|
||||
"redirect_uris": ["https://myapp.com/callback"]
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Store the `client_secret` securely — it cannot be retrieved later.
|
||||
</Warning>
|
||||
|
||||
## النطاقات
|
||||
|
||||
| Scope | الوصول |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| `api` | Full read/write access to the Core and Metadata APIs |
|
||||
| `profile` | Read the authenticated user's profile information |
|
||||
|
||||
Request scopes as a space-separated string: `scope=api profile`
|
||||
|
||||
## Authorization Code Flow
|
||||
|
||||
Use this flow when your app acts on behalf of a Twenty user.
|
||||
|
||||
### 1. Redirect the user to authorize
|
||||
|
||||
```
|
||||
GET /oauth/authorize?
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
response_type=code&
|
||||
redirect_uri=https://myapp.com/callback&
|
||||
scope=api&
|
||||
state=random_state_value&
|
||||
code_challenge=CHALLENGE&
|
||||
code_challenge_method=S256
|
||||
```
|
||||
|
||||
| المعلمة | مطلوب | الوصف |
|
||||
| ----------------------- | -------- | ------------------------------------------------------------ |
|
||||
| `client_id` | نعم | Your registered client ID |
|
||||
| `response_type` | نعم | Must be `code` |
|
||||
| `redirect_uri` | نعم | Must match a registered redirect URI |
|
||||
| `scope` | لا | Space-separated scopes (defaults to `api`) |
|
||||
| `الحالة` | مُوصى به | Random string to prevent CSRF attacks |
|
||||
| `code_challenge` | مُوصى به | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) |
|
||||
| `code_challenge_method` | مُوصى به | Must be `S256` when using PKCE |
|
||||
|
||||
The user sees a consent screen and approves or denies access.
|
||||
|
||||
### ٢. Handle the callback
|
||||
|
||||
After authorization, Twenty redirects back to your `redirect_uri`:
|
||||
|
||||
```
|
||||
https://myapp.com/callback?code=AUTH_CODE&state=random_state_value
|
||||
```
|
||||
|
||||
Verify that `state` matches what you sent.
|
||||
|
||||
### ٣. Exchange the code for tokens
|
||||
|
||||
```bash
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&
|
||||
code=AUTH_CODE&
|
||||
redirect_uri=https://myapp.com/callback&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET&
|
||||
code_verifier=YOUR_PKCE_VERIFIER
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbG...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "dGhpcyBpcyBh..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Use the access token
|
||||
|
||||
```bash
|
||||
GET /rest/companies
|
||||
Authorization: Bearer ACCESS_TOKEN
|
||||
```
|
||||
|
||||
### 5. Refresh when expired
|
||||
|
||||
```bash
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=refresh_token&
|
||||
refresh_token=YOUR_REFRESH_TOKEN&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET
|
||||
```
|
||||
|
||||
## Client Credentials Flow
|
||||
|
||||
For server-to-server integrations with no user interaction:
|
||||
|
||||
```bash
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET&
|
||||
scope=api
|
||||
```
|
||||
|
||||
The returned token has workspace-level access, not tied to any specific user.
|
||||
|
||||
## Server Discovery
|
||||
|
||||
Twenty publishes its OAuth configuration at a standard discovery endpoint:
|
||||
|
||||
```
|
||||
GET /.well-known/oauth-authorization-server
|
||||
```
|
||||
|
||||
This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients.
|
||||
|
||||
## API Endpoints Summary
|
||||
|
||||
| نقطة النهاية | الغرض |
|
||||
| ----------------------------------------- | --------------------------- |
|
||||
| `/.well-known/oauth-authorization-server` | Server metadata discovery |
|
||||
| `/oauth/register` | Dynamic client registration |
|
||||
| `/oauth/authorize` | User authorization |
|
||||
| `/oauth/token` | Token exchange and refresh |
|
||||
|
||||
| البيئة | عنوان URL الأساسي |
|
||||
| --------------------- | ------------------------ |
|
||||
| **السحابة** | `https://api.twenty.com` |
|
||||
| **الاستضافة الذاتية** | `https://{your-domain}` |
|
||||
|
||||
## OAuth vs API Keys
|
||||
|
||||
| | مفاتيح واجهة برمجة التطبيقات | OAuth |
|
||||
| ------------------ | ---------------------------- | -------------------------------------- |
|
||||
| **الإعداد** | Generate in Settings | Register a client, implement flow |
|
||||
| **User context** | None (workspace-level) | Specific user's permissions |
|
||||
| **الأفضل لـ** | Scripts, internal tools | External apps, multi-user integrations |
|
||||
| **Token rotation** | يدوي | Automatic via refresh tokens |
|
||||
| **Scoped access** | Full API access | Granular via scopes |
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
---
|
||||
title: خطافات الويب
|
||||
description: استقبل إشعارات في الوقت الفعلي عند وقوع أحداث في نظام إدارة علاقات العملاء (CRM) الخاص بك.
|
||||
icon: satellite-dish
|
||||
description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
تدفع خطافات الويب البيانات إلى أنظمتك في الوقت الفعلي عند وقوع أحداث في Twenty — دون الحاجة إلى الاستطلاع الدوري. استخدمها للحفاظ على تزامن الأنظمة الخارجية، وتشغيل الأتمتة، أو إرسال التنبيهات.
|
||||
Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects.
|
||||
|
||||
## إنشاء خطاف ويب
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,28 @@
|
|||
---
|
||||
title: البدء
|
||||
description: مرحبًا بك في وثائق المطوّرين الخاصة بـ Twenty، مرجعك للتوسيع والاستضافة الذاتية والمساهمة في Twenty.
|
||||
title: المطورون
|
||||
description: Build apps, use the API, self-host, or contribute to the codebase.
|
||||
---
|
||||
|
||||
import { CardTitle } from "/snippets/card-title.mdx"
|
||||
|
||||
<CardGroup cols={٣}>
|
||||
<Card href="/l/ar/developers/extend/extend" img="/images/user-guide/integrations/plug.png">
|
||||
<CardTitle>التوسيع</CardTitle>
|
||||
أنشئ عمليات تكامل مع واجهات برمجة التطبيقات وخطافات الويب والتطبيقات المخصصة.
|
||||
<Card href="/l/ar/developers/extend/apps/getting-started" img="/images/user-guide/halftone/dev-apps.png">
|
||||
<CardTitle>Apps</CardTitle>
|
||||
Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/ar/developers/self-host/self-host" img="/images/user-guide/what-is-twenty/20.png">
|
||||
<CardTitle>الاستضافة الذاتية</CardTitle>
|
||||
قم بنشر Twenty وإدارته على البنية التحتية الخاصة بك.
|
||||
<Card href="/l/ar/developers/extend/api" img="/images/user-guide/halftone/dev-api.png">
|
||||
<CardTitle>API</CardTitle>
|
||||
REST and GraphQL APIs, webhooks, and OAuth.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/ar/developers/contribute/contribute" img="/images/user-guide/github/github-header.png">
|
||||
<CardTitle>المساهمة</CardTitle>
|
||||
انضم إلى مجتمعنا مفتوح المصدر وساهم في Twenty.
|
||||
<Card href="/l/ar/developers/self-host/capabilities/docker-compose" img="/images/user-guide/halftone/dev-self-host.png">
|
||||
<CardTitle>Self-Host</CardTitle>
|
||||
Run Twenty on your own infrastructure.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/ar/developers/contribute/capabilities/local-setup" img="/images/user-guide/halftone/dev-contribute.png">
|
||||
<CardTitle>Contribute</CardTitle>
|
||||
Set up the monorepo locally and submit PRs.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: طرق أخرى
|
||||
icon: cloud
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: بنقرة واحدة مع Docker Compose
|
||||
title: Docker Compose
|
||||
icon: docker
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: إعداد
|
||||
icon: gear
|
||||
---
|
||||
|
||||
# إدارة الإعدادات
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: استكشاف الأخطاء وإصلاحها
|
||||
icon: wrench
|
||||
---
|
||||
|
||||
## استكشاف الأخطاء وإصلاحها
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: دليل الترقية
|
||||
icon: arrow-up-right-dots
|
||||
---
|
||||
|
||||
## إرشادات عامة
|
||||
|
|
@ -16,366 +17,14 @@ title: دليل الترقية
|
|||
|
||||
3. قم بإعادة تشغيل Twenty باستخدام `docker compose up -d`
|
||||
|
||||
إذا كنت ترغب في ترقية مثيلك بزيادة بعض الإصدارات، مثل الانتقال من v0.33.0 إلى v0.35.0، يجب أن تقوم بترقية مثيلك بشكل تسلسلي، في هذا المثال من v0.33.0 إلى v0.34.0، ثم من v0.34.0 إلى v0.35.0.
|
||||
|
||||
**تأكد من أن لديك نسخة احتياطية غير تالفة بعد كل إصدار تمت ترقيته.**
|
||||
|
||||
## خطوات الترقية الخاصة بالإصدار
|
||||
|
||||
## v1.0
|
||||
## After v1.21
|
||||
|
||||
مرحباً Twenty v1.0! 🎉
|
||||
We know support sequential upgrades. You don't need to go through each version one by one.
|
||||
|
||||
## v0.60
|
||||
## Before v1.21
|
||||
|
||||
### تحسين الأداء
|
||||
|
||||
تم تحسين جميع التفاعلات مع واجهة برمجة التطبيقات للبيانات الوصفية للحصول على أداء أفضل، خاصة فيما يتعلق بمعالجة بيانات الكائن وإنشاء المساحات.
|
||||
|
||||
أعدنا تصميم استراتيجيتنا للتخزين المؤقت لإعطاء الأولوية للوصول عبر التخزين المؤقت على استعلامات قاعدة البيانات قدر الإمكان، مما أدى إلى تحسين كبير في أداء عمليات واجهة برمجة التطبيقات للبيانات الوصفية.
|
||||
|
||||
إذا واجهت أي مشاكل في وقت التشغيل بعد الترقية، قد تحتاج إلى مسح التخزين المؤقت لضمان تزامنه مع أحدث التغييرات. قم بتشغيل هذا الأمر في حاوية خادم twenty الخاص بك:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
### v0.55
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.55
|
||||
|
||||
لم تعد بحاجة إلى تشغيل أي أمر، الصورة الجديدة ستعتني بتشغيل جميع الترحيلات المطلوبة تلقائيًا.
|
||||
|
||||
### خطأ: `User does not have permission`
|
||||
|
||||
إذا واجهت أخطاء في الأذونات في معظم الطلبات بعد الترقية، فقد تحتاج إلى مسح التخزين المؤقت لإعادة حساب أحدث الأذونات.
|
||||
|
||||
في حاوية خادم `twenty` الخاص بك، قم بتشغيل:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
هذه المشكلة خاصة بهذا الإصدار من Twenty ولا يجب أن تكون ضرورية في الترقيات المستقبلية.
|
||||
|
||||
### v0.54
|
||||
|
||||
منذ الإصدار `0.53`، لا حاجة لأي إجراءات يدوية.
|
||||
|
||||
#### إيقاف تشغيل مخطط البيانات الوصفية
|
||||
|
||||
قمنا بدمج مخطط `metadata` مع مخطط `core` لتبسيط استرجاع البيانات من `TypeORM`.
|
||||
قمنا بدمج خطوة تنفيذ الأمر `migrate` مع الأمر `upgrade`. لا ننصح بتشغيل `migrate` يدويًا داخل أي من حاويات الخادم/العمل الخاصة بك.
|
||||
|
||||
### منذ v0.53
|
||||
|
||||
بدءًا من الإصدار `0.53`، تتم الترقية بشكل برمجي داخل `DockerFile`، مما يعني أنه من الآن فصاعدًا، لن تحتاج إلى تشغيل أي أوامر يدويًا بعد الآن.
|
||||
|
||||
تأكد من متابعة الترقية الخاصة بك تسلسليًا، دون تخطي أي إصدار رئيسي (على سبيل المثال `0.43.3` إلى `0.44.0` مسموح، ولكن `0.43.1` إلى `0.45.0` غير مسموح)، قد يؤدي بخلاف ذلك إلى عدم تزامن إصدار مساحة العمل مما قد يؤدي إلى خطأ في وقت التشغيل وفقدان الوظائف.
|
||||
|
||||
للتحقق مما إذا كانت مساحة العمل قد تمت ترقيتها بشكل صحيح ، يمكنك مراجعة نسختها في قاعدة البيانات في جدول `core.workspace`.
|
||||
|
||||
يجب أن تكون دائمًا في نطاق إصدار `major.minor` لحساب Twenty الحالي الخاص بك ، ويمكنك مشاهدة نسخة حسابك في لوحة المدير (في `/settings/admin-panel`، يمكن الوصول إليها إذا كانت خاصية `canAccessFullAdminPanel` الخاصة بالمستخدم مصفوفة إلى true في قاعدة البيانات) أو عن طريق تشغيل `echo $APP_VERSION` في حاوية `twenty-server` الخاصة بك.
|
||||
|
||||
لإصلاح إصدار مساحة العمل غير المتزامن ، سيتعين عليك الترقية من الإصدار المعني لـ Twenty باتباع دليل الترقية الخاص ذو الصلة تسلسليًا وهكذا حتى يصل إلى الإصدار المطلوب.
|
||||
|
||||
#### إزالة `auditLog`
|
||||
|
||||
لقد قمنا بإزالة كائن المعيار auditLog، مما يعني أن حجم النسخة الاحتياطية الخاصة بك قد يقل بشكل كبير بعد هذه الترقية.
|
||||
|
||||
### من v0.51 إلى v0.52
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.52
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### لدي مساحة عمل محظورة في الإصدار بين `0.52.0` و`0.52.6`
|
||||
|
||||
لسوء الحظ، تم إزالة `0.52.0` و`0.52.6` بالكامل من dockerHub.
|
||||
سيتعين عليك تحديث نسخة مساحة العمل يدويًا إلى `0.51.0` في قاعدة البيانات والترقية باستخدام إصدار twenty عند `0.52.11` باتباع دليل الترقية الخاص به أعلاه.
|
||||
|
||||
### من v0.50 إلى v0.51
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.51
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### من v0.44.0 إلى v0.50.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.50.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### تغيير ملف docker-compose.yml
|
||||
|
||||
يتضمن هذا الإصدار تغييرًا في `docker-compose.yml` لمنح خدمة `worker` إمكانية الوصول إلى وحدة التخزين `server-local-data`.
|
||||
يرجى تحديث `docker-compose.yml` المحلي الخاص بك بـ [docker-compose.yml v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
|
||||
|
||||
### من v0.43.0 إلى v0.44.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.44.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### من v0.42.0 إلى v0.43.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.43.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
في هذا الإصدار، قمنا أيضًا بالتحول إلى صورة postgres:16 في docker-compose.yml.
|
||||
|
||||
#### (الخيار 1) ترحيل قاعدة البيانات
|
||||
|
||||
احتفاظ بصورة postgres-spilo الحالية مقبول، ولكن سيتعين عليك تجميد الإصدار في docker-compose.yml ليكون 0.43.0.
|
||||
|
||||
#### (الخيار 2) ترحيل قاعدة البيانات
|
||||
|
||||
إذا كنت تريد ترحيل قاعدة بياناتك إلى الصورة الجديدة postgres:16، يرجى اتباع هذه الخطوات:
|
||||
|
||||
1. نسخ قاعدة البيانات الخاصة بك من حاوية postgres-spilo القديمة
|
||||
|
||||
```
|
||||
docker exec -it twenty-db-1 sh
|
||||
pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql
|
||||
exit
|
||||
docker cp twenty-db-1:/home/postgres/databases_backup.sql .
|
||||
```
|
||||
|
||||
تأكد من أن ملف النسخ الاحتياطي ليس فارغًا.
|
||||
|
||||
2. قم بترقية docker-compose.yml الخاص بك لاستخدام صورة postgres:16 كما هو في الملف [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml).
|
||||
|
||||
3. استعادة قاعدة البيانات إلى الحاوية الجديدة postgres:16
|
||||
|
||||
```
|
||||
docker cp databases_backup.sql twenty-db-1:/databases_backup.sql
|
||||
docker exec -it twenty-db-1 sh
|
||||
psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql
|
||||
exit
|
||||
```
|
||||
|
||||
### من v0.41.0 إلى v0.42.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.42.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.42
|
||||
```
|
||||
|
||||
**متغيرات البيئة**
|
||||
|
||||
* تمت الإزالة: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
|
||||
* تمت الإضافة: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
|
||||
|
||||
### من v0.40.0 إلى v0.41.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.41.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.41
|
||||
```
|
||||
|
||||
**متغيرات البيئة**
|
||||
|
||||
* تمت الإزالة: `AUTH_MICROSOFT_TENANT_ID`
|
||||
|
||||
### من v0.35.0 إلى v0.40.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.40.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.40
|
||||
```
|
||||
|
||||
**متغيرات البيئة**
|
||||
|
||||
* تمت الإضافة: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
|
||||
|
||||
### من v0.34.0 إلى v0.35.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.35.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.35
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.35` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
**متغيرات البيئة**
|
||||
|
||||
* قمنا باستبدال `ENABLE_DB_MIGRATIONS` بـ `DISABLE_DB_MIGRATIONS` (القيمة الافتراضية الآن `false`, على الأرجح لن تحتاج إلى تعيين أي شيء)
|
||||
|
||||
### من v0.33.0 إلى v0.34.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.34.0
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.34
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.34` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
**متغيرات البيئة**
|
||||
|
||||
* تمت الإزالة: `FRONT_BASE_URL`
|
||||
* تمت الإضافة: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
|
||||
|
||||
لقد قمنا بتحديث الطريقة التي نتعامل بها مع عنوان URL الخاص بالواجهة الأمامية.
|
||||
يمكنك الآن تعيين عنوان URL الخاص بالواجهة الأمامية باستخدام متغيرات `FRONT_DOMAIN`, `FRONT_PROTOCOL` و`FRONT_PORT`.
|
||||
إذا لم يتم تعيين FRONT_DOMAIN، فسوف يتراجع عنوان URL للواجهة الأمامية إلى `SERVER_URL`.
|
||||
|
||||
### من v0.32.0 إلى v0.33.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.33.0
|
||||
|
||||
```
|
||||
yarn command:prod cache:flush
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.33
|
||||
```
|
||||
|
||||
أمر `yarn command:prod cache:flush` سيقوم بمسح ذاكرة تخزين Redis المؤقتة.
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.33` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
بدءًا من هذا الإصدار، أصبحت صورة twenty-postgres للقاعدة غير نشطة وتم استخدام twenty-postgres-spilo بدلاً منها.
|
||||
إذا كنت ترغب في الاستمرار باستخدام صورة twenty-postgres، فما عليك سوى استبدال `twentycrm/twenty-postgres:${TAG}` بـ `twentycrm/twenty-postgres` في docker-compose.yml.
|
||||
|
||||
### من v0.31.0 إلى v0.32.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.32.0
|
||||
|
||||
**ترقية المخطط والبيانات**
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.32
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.32` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
**متغيرات البيئة**
|
||||
|
||||
لقد قمنا بتحديث الطريقة التي نتعامل بها مع اتصال Redis.
|
||||
|
||||
* تمت الإزالة: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
|
||||
* تمت الإضافة: `REDIS_URL`
|
||||
|
||||
قم بتحديث ملفك `.env` لاستخدام المتغير الجديد `REDIS_URL` بدلاً من معلمات اتصال Redis الفردية.
|
||||
|
||||
قمنا أيضًا بتبسيط الطريقة التي نتعامل بها مع رموز JWT.
|
||||
|
||||
* تمت الإزالة: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
|
||||
* تمت الإضافة: `APP_SECRET`
|
||||
|
||||
قم بتحديث ملفك `.env` لاستخدام المتغير الجديد `APP_SECRET` بدلاً من الأسرار الفردية للرموز (يمكنك استخدام نفس السر كما كان من قبل أو توليد سلسلة عشوائية جديدة)
|
||||
|
||||
**الحساب المتصل**
|
||||
|
||||
إذا كنت تستخدم حسابًا متصلًا لمزامنة رسائل بريدك الإلكتروني في جوجل والتقويمات، فستحتاج إلى تفعيل [People API](https://developers.google.com/people) في وحدة تحكم مشرف جوجل لديك.
|
||||
|
||||
### من v0.30.0 إلى v0.31.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.31.0
|
||||
|
||||
**ترقية المخطط والبيانات**:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.31
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.31` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
### من v0.24.0 إلى v0.30.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.30.0
|
||||
|
||||
**تغيير كبير**:
|
||||
لتحسين الأداء، يتطلب Twenty الآن تكوين Redis للتخزين المؤقت. قمنا بتحديث [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) لتعكس ذلك.
|
||||
تأكد من تحديث إعدادات التكوين الخاصة بك وتحديث المتغيرات البيئية الخاصة بك وفقًا لذلك:
|
||||
|
||||
```
|
||||
REDIS_HOST={your-redis-host}
|
||||
REDIS_PORT={your-redis-port}
|
||||
CACHE_STORAGE_TYPE=redis
|
||||
```
|
||||
|
||||
**ترقية المخطط والبيانات**:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.30
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.30` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
### من v0.23.0 إلى v0.24.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.24.0
|
||||
|
||||
قم بتشغيل الأوامر التالية:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.24
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata)
|
||||
أمر `yarn command:prod upgrade-0.24` يتولى ترقية البيانات إلى جميع المساحات.
|
||||
|
||||
### من v0.22.0 إلى v0.23.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.23.0
|
||||
|
||||
قم بتشغيل الأوامر التالية:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.23
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على قاعدة البيانات.
|
||||
أمر `yarn command:prod upgrade-0.23` يتولى ترقية البيانات، بما في ذلك نقل الأنشطة إلى المهام/الملاحظات.
|
||||
|
||||
### من v0.21.0 إلى v0.22.0
|
||||
|
||||
قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.22.0
|
||||
|
||||
قم بتشغيل الأوامر التالية:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod workspace:sync-metadata -f
|
||||
yarn command:prod upgrade-0.22
|
||||
```
|
||||
|
||||
أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على قاعدة البيانات.
|
||||
الأمر `yarn command:prod workspace:sync-metadata -f` سيزامن تعريف الكائنات القياسية مع جداول البيانات الوصفية ويطبق الترقيات المطلوبة على مساحات العمل الموجودة.
|
||||
الأمر `yarn command:prod upgrade-0.22` سيقوم بتطبيق تحويلات بيانات محددة للتكيف مع الخيارات الافتراضية الجديدة لتوثيق الطلبات في الكائنات.
|
||||
Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.).
|
||||
|
|
|
|||
|
|
@ -1,24 +1,27 @@
|
|||
{
|
||||
"tabs": {
|
||||
"gettingStarted": {
|
||||
"label": "البدء",
|
||||
"groups": {
|
||||
"welcome": {
|
||||
"label": "Welcome"
|
||||
},
|
||||
"coreConcepts": {
|
||||
"label": "Core Concepts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGuide": {
|
||||
"label": "دليل المستخدم",
|
||||
"groups": {
|
||||
"discoverTwenty": {
|
||||
"label": "اكتشف Twenty",
|
||||
"groups": {
|
||||
"gettingStartedCapabilities": {
|
||||
"label": "القدرات"
|
||||
},
|
||||
"gettingStartedHowTos": {
|
||||
"label": "الإرشادات"
|
||||
}
|
||||
}
|
||||
"userGuideOverview": {
|
||||
"label": "نظرة عامة"
|
||||
},
|
||||
"dataModel": {
|
||||
"label": "نموذج البيانات",
|
||||
"groups": {
|
||||
"dataModelCapabilities": {
|
||||
"label": "القدرات"
|
||||
"dataModelReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"dataModelHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -28,8 +31,8 @@
|
|||
"dataMigration": {
|
||||
"label": "ترحيل البيانات",
|
||||
"groups": {
|
||||
"dataMigrationCapabilities": {
|
||||
"label": "القدرات"
|
||||
"dataMigrationReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"dataMigrationHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -39,8 +42,8 @@
|
|||
"calendarEmails": {
|
||||
"label": "التقويم والبريد الإلكتروني",
|
||||
"groups": {
|
||||
"calendarEmailsCapabilities": {
|
||||
"label": "القدرات"
|
||||
"calendarEmailsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"calendarEmailsHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -50,8 +53,8 @@
|
|||
"workflows": {
|
||||
"label": "سير العمل",
|
||||
"groups": {
|
||||
"workflowsCapabilities": {
|
||||
"label": "القدرات"
|
||||
"workflowsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"workflowsHowTos": {
|
||||
"label": "الإرشادات",
|
||||
|
|
@ -75,21 +78,26 @@
|
|||
"ai": {
|
||||
"label": "الذكاء الاصطناعي",
|
||||
"groups": {
|
||||
"aiCapabilities": {
|
||||
"label": "القدرات"
|
||||
"aiReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"aiHowTos": {
|
||||
"label": "الإرشادات"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsPipelines": {
|
||||
"label": "طرق العرض والمسارات",
|
||||
"layout": {
|
||||
"label": "التخطيط",
|
||||
"groups": {
|
||||
"viewsPipelinesCapabilities": {
|
||||
"label": "القدرات"
|
||||
"layoutReference": {
|
||||
"label": "Reference",
|
||||
"groups": {
|
||||
"layoutViews": {
|
||||
"label": "العروض"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsPipelinesHowTos": {
|
||||
"layoutHowTos": {
|
||||
"label": "الإرشادات"
|
||||
}
|
||||
}
|
||||
|
|
@ -97,8 +105,8 @@
|
|||
"dashboards": {
|
||||
"label": "لوحات القيادة",
|
||||
"groups": {
|
||||
"dashboardsCapabilities": {
|
||||
"label": "القدرات"
|
||||
"dashboardsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"dashboardsHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -108,8 +116,8 @@
|
|||
"permissionsAccess": {
|
||||
"label": "الصلاحيات والوصول",
|
||||
"groups": {
|
||||
"permissionsAccessCapabilities": {
|
||||
"label": "القدرات"
|
||||
"permissionsAccessReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"permissionsAccessHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -119,8 +127,8 @@
|
|||
"billing": {
|
||||
"label": "الفوترة",
|
||||
"groups": {
|
||||
"billingCapabilities": {
|
||||
"label": "القدرات"
|
||||
"billingReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"billingHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -130,8 +138,8 @@
|
|||
"settings": {
|
||||
"label": "\\ا\\ل\\إ\\ع\\د\\ا\\د\\ا\\ت",
|
||||
"groups": {
|
||||
"settingsCapabilities": {
|
||||
"label": "القدرات"
|
||||
"settingsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"settingsHowTos": {
|
||||
"label": "الإرشادات"
|
||||
|
|
@ -143,59 +151,20 @@
|
|||
"developers": {
|
||||
"label": "المطورون",
|
||||
"groups": {
|
||||
"developersGroup": {
|
||||
"label": "المطورون"
|
||||
"developersOverview": {
|
||||
"label": "نظرة عامة"
|
||||
},
|
||||
"extend": {
|
||||
"label": "التوسيع",
|
||||
"groups": {
|
||||
"apps": {
|
||||
"label": "التطبيقات"
|
||||
}
|
||||
}
|
||||
"apps": {
|
||||
"label": "التطبيقات"
|
||||
},
|
||||
"api": {
|
||||
"label": "واجهة برمجة التطبيقات"
|
||||
},
|
||||
"selfHost": {
|
||||
"label": "الاستضافة الذاتية",
|
||||
"groups": {
|
||||
"selfHostCapabilities": {
|
||||
"label": "القدرات"
|
||||
}
|
||||
}
|
||||
"label": "الاستضافة الذاتية"
|
||||
},
|
||||
"contribute": {
|
||||
"label": "المساهمة",
|
||||
"groups": {
|
||||
"contributeCapabilities": {
|
||||
"label": "القدرات",
|
||||
"groups": {
|
||||
"frontendDevelopment": {
|
||||
"label": "تطوير الواجهة الأمامية",
|
||||
"groups": {
|
||||
"twentyUi": {
|
||||
"label": "Twenty UI",
|
||||
"groups": {
|
||||
"display": {
|
||||
"label": "عرض"
|
||||
},
|
||||
"feedback": {
|
||||
"label": "التغذية الراجعة"
|
||||
},
|
||||
"input": {
|
||||
"label": "إدخال"
|
||||
},
|
||||
"navigation": {
|
||||
"label": "التنقل"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"backendDevelopment": {
|
||||
"label": "تطوير الواجهة الخلفية"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"label": "المساهمة"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: تلميح التطبيق
|
||||
icon: رسالة
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: علامة صحيح
|
||||
icon: circle-check
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: الأيقونات
|
||||
icon: الأيقونات
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
title: شارة قريبًا
|
||||
---
|
||||
|
||||
|
||||
شارة صغيرة أو "كبسولة" للإشارة إلى أن شيئًا ما قادم قريبًا.
|
||||
|
||||
```jsx
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: علامة
|
||||
icon: علامة
|
||||
---
|
||||
|
||||
|
||||
مكوّن لتصنيف المحتوى أو وسمه بصريًا.
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: الأزرار
|
||||
icon: hand-pointer
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: مربع اختيار
|
||||
icon: square-check
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: طريقة عرض الألوان
|
||||
icon: لوحة الألوان
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: راديو
|
||||
icon: circle-dot
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: تبديل
|
||||
icon: toggle-on
|
||||
---
|
||||
|
||||
|
||||
<Tabs>
|
||||
<Tab title="الاستخدام">
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: نظرة عامة
|
||||
icon: لوحة الألوان
|
||||
description: مكتبة المكونات لتطبيق Twenty CRM
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: التنقل
|
||||
icon: compass
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: روابط
|
||||
icon: رابط
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: عنصر قائمة
|
||||
icon: bars
|
||||
---
|
||||
|
||||
|
||||
عنصر قائمة متعدد الاستخدامات مصمم للاستخدام في قائمة أو قائمة تنقل.
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: شريط التنقل
|
||||
icon: bars
|
||||
---
|
||||
|
||||
|
||||
يستعرض شريط التنقل الذي يحتوي على عدة مكونات `NavigationBarItem`.
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
title: التغذية الراجعة
|
||||
---
|
||||
|
||||
|
||||
يشير إلى تقدم أو عد تنازلي ويتحرك من اليمين إلى اليسار.
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: الفوترة
|
|||
description: تعرّف على تسعير Twenty وأدِر اشتراكك.
|
||||
---
|
||||
|
||||
|
||||
تقدّم Twenty خطط تسعير مرنة لتلبية احتياجات فريقك. أدِر اشتراكك، وتتبع أرصدة سير العمل، واطّلع على الفواتير — وكل ذلك من **Settings → Billing**.
|
||||
|
||||
## ما الذي يتضمنه هذا القسم
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: Calendar & Emails
|
|||
description: Connect your email and calendar accounts to Twenty.
|
||||
---
|
||||
|
||||
|
||||
## Connection Options
|
||||
|
||||
### حساب Google (Gmail وتقويم Google)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: لوحات القيادة
|
|||
description: تعلّم أساسيات إعداد التقارير ولوحات المعلومات في Twenty.
|
||||
---
|
||||
|
||||
|
||||
<Note>
|
||||
لوحات المعلومات حاليًا في الإصدار التجريبي. قم بتفعيلها ضمن **الإعدادات → التحديثات → الوصول المبكر**.
|
||||
</Note>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ description: استيراد وتصدير بيانات CRM عبر ملفات CSV
|
|||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
|
||||
## طرق الاستيراد
|
||||
|
||||
تدعم Twenty طريقتين رئيستين لاستيراد البيانات:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: نموذج البيانات
|
|||
description: تعرّف إلى نموذج البيانات وكيفية تصميم نموذج يناسب نشاطك التجاري.
|
||||
---
|
||||
|
||||
|
||||
## ما هو نموذج البيانات؟
|
||||
|
||||
نموذج البيانات هو الهيكل الذي يحدّد كيفية تنظيم المعلومات في نظام إدارة علاقات العملاء (CRM) لديك. فكّر فيه باعتباره **المخطط** لبيانات عملائك — تصمّمه مرة واحدة، ثم تملؤه ببياناتك الفعلية.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
---
|
||||
title: اكتشف Twenty
|
||||
title: دليل المستخدم
|
||||
description: مرحباً بك في دليل مستخدم Twenty، مصادرك للإعدادات المتقدمة وأفضل الممارسات.
|
||||
---
|
||||
|
||||
import { CardTitle } from "/snippets/card-title.mdx"
|
||||
|
||||
<CardGroup cols={٣}>
|
||||
<Card href="/l/ar/user-guide/getting-started/capabilities/what-is-twenty" img="/images/user-guide/api/api.png">
|
||||
<CardTitle>اكتشف Twenty</CardTitle>
|
||||
تعرّف على ماهية Twenty وكيف يمكن أن تساعد عملك.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/ar/user-guide/data-model/overview" img="/images/user-guide/halftone/data-model.png">
|
||||
<CardTitle>نموذج البيانات</CardTitle>
|
||||
خصّص نموذج بياناتك ليتوافق مع عمليات عملك.
|
||||
|
|
@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx"
|
|||
عزّز فريقك بوكلاء الذكاء الاصطناعي.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/ar/user-guide/views-pipelines/overview" img="/images/user-guide/halftone/layout.png">
|
||||
<CardTitle>طرق العرض والمسارات</CardTitle>
|
||||
نظّم بياناتك من خلال طرق عرض عملية ومسارات.
|
||||
<Card href="/l/ar/user-guide/layout/overview" img="/images/user-guide/halftone/layout.png">
|
||||
<CardTitle>Layout</CardTitle>
|
||||
Navigation, views, and record page customization.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/ar/user-guide/dashboards/overview" img="/images/user-guide/halftone/dashboards.png">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: التنقل
|
||||
description: Customize the left sidebar to match how your team works.
|
||||
---
|
||||
|
||||
The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page.
|
||||
|
||||
## Reordering items
|
||||
|
||||
Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar.
|
||||
|
||||
## مجلدات
|
||||
|
||||
Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects.
|
||||
|
||||
To create a folder, right-click in the sidebar or use the `+` button.
|
||||
|
||||
## Hiding objects
|
||||
|
||||
Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model.
|
||||
|
||||
## المفضلات
|
||||
|
||||
Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own.
|
||||
|
||||
## Custom links
|
||||
|
||||
Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly.
|
||||
|
||||
## Command menu
|
||||
|
||||
Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar.
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
title: صفحات السجل},{
|
||||
description: خصص تخطيط صفحات تفاصيل كل سجل باستخدام علامات تبويب وعناصر واجهة.
|
||||
---
|
||||
|
||||
عند فتح سجل في Twenty، تتكون صفحة التفاصيل من **علامات تبويب** و**عناصر واجهة**. كلاهما قابل للتخصيص بالكامل حسب نوع الكائن.
|
||||
|
||||
## علامات التبويب
|
||||
|
||||
يمكن أن تحتوي كل صفحة سجل على عدة علامات تبويب — مشابهة لعلامات التبويب في المتصفح. استخدمها لتنظيم الجوانب المختلفة للسجل. على سبيل المثال، قد يحتوي سجل شركة على علامات تبويب مثل نظرة عامة والاتصالات والمهام والملفات.
|
||||
|
||||
يمكنك:
|
||||
|
||||
* إضافة علامات التبويب وإزالتها
|
||||
* إعادة تسمية علامات التبويب
|
||||
* أعد ترتيب علامات التبويب بالسحب
|
||||
* حدد علامة التبويب التي تظهر افتراضيًا
|
||||
|
||||
## الأدوات
|
||||
|
||||
الأدوات هي اللبنات الأساسية داخل كل علامة تبويب. تشمل أنواع الأدوات المتاحة:
|
||||
|
||||
| أداة | ما الذي تعرضه |
|
||||
| ----------------------- | ----------------------------------------- |
|
||||
| **حقول** | حقول السجل، مجمّعة أو بشكل فردي |
|
||||
| **السجلات المرتبطة** | جدول سجلات مرتبطة عبر علاقة |
|
||||
| **الرسائل الإلكترونية** | سجل البريد الإلكتروني من الحسابات المتصلة |
|
||||
| **التقويم** | أحداث التقويم المرتبطة بالسجل |
|
||||
| **الجدول الزمني** | سجل الأنشطة والأحداث |
|
||||
| **المهام** | المهام المرتبطة |
|
||||
| **الملاحظات** | ملاحظات بنص منسق |
|
||||
| **الملفات** | مرفقات الملفات |
|
||||
| **الرسوم البيانية** | بيانات مرئية من السجلات المرتبطة |
|
||||
| **iFrame** | محتوى خارجي مُضمّن |
|
||||
| **نص منسق** | محتوى ثابت أو أوصاف |
|
||||
|
||||
## تخصيص صفحة السجل
|
||||
|
||||
1. افتح أي سجل
|
||||
2. اضغط على `Cmd+K` وابحث عن "تحرير تخطيط صفحة السجل"
|
||||
3. أنت الآن في وضع التخصيص:
|
||||
* **أضف أدوات** من منتقي الأدوات
|
||||
* **اسحب الأدوات** لإعادة وضعها على الشبكة
|
||||
* **غيّر حجم الأدوات** بسحب حوافها
|
||||
* **اضبط الحقول** المعروضة داخل كل أداة
|
||||
* **أدِر علامات التبويب** — أضف، أزل، أعد التسمية، وأعد الترتيب
|
||||
4. احفظ تغييراتك — سيتم تطبيقها على جميع السجلات لذلك النوع من الكائنات
|
||||
|
||||
## ظهور الحقول
|
||||
|
||||
ضمن أداة الحقول، يمكنك التحكّم في أي الحقول مرئية وبأي ترتيب. يتيح لك ذلك إنشاء تخطيطات مركّزة — على سبيل المثال، إظهار الحقول الأهم فقط في علامة تبويب نظرة عامة ووضع الحقول التفصيلية في علامة تبويب منفصلة.
|
||||
45
packages/twenty-docs/l/ar/user-guide/layout/overview.mdx
Normal file
45
packages/twenty-docs/l/ar/user-guide/layout/overview.mdx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: التخطيط
|
||||
description: Customize how you navigate, browse, and view records in Twenty.
|
||||
---
|
||||
|
||||
Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record.
|
||||
|
||||
## التنقل
|
||||
|
||||
The left sidebar is fully customizable. يمكنك:
|
||||
|
||||
* **Reorder items** by dragging and dropping
|
||||
* **Create folders** to group related objects and views
|
||||
* **Hide objects** you don't use
|
||||
* **Add custom links** to external tools
|
||||
* **Pin favorites** for quick access to views, records, or searches
|
||||
|
||||
[Navigation reference →](/l/ar/user-guide/layout/capabilities/navigation)
|
||||
|
||||
## العروض
|
||||
|
||||
Views control how lists of records are displayed. Twenty supports three view types:
|
||||
|
||||
| عرض | Best for |
|
||||
| ------------ | ---------------------------------------------------------------------- |
|
||||
| **Table** | Working with many records at once — spreadsheet-style rows and columns |
|
||||
| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage |
|
||||
| **Calendar** | Time-based planning — records plotted by a date field |
|
||||
|
||||
Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private.
|
||||
|
||||
[Table views →](/l/ar/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/ar/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/ar/user-guide/views-pipelines/capabilities/calendar-view)
|
||||
|
||||
## Record pages
|
||||
|
||||
When you open a record, the detail page is built from configurable tabs and widgets. يمكنك:
|
||||
|
||||
* **Add, remove, and reorder tabs** on any record type
|
||||
* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more
|
||||
* **Drag and resize widgets** on a grid layout
|
||||
* **Control field visibility** per widget
|
||||
|
||||
Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout").
|
||||
|
||||
[Record pages reference →](/l/ar/user-guide/layout/capabilities/record-pages)
|
||||
|
|
@ -3,7 +3,6 @@ title: الأذونات والوصول
|
|||
description: إدارة الأدوار والأذونات والتحكم في الوصول ضمن مساحة العمل.
|
||||
---
|
||||
|
||||
|
||||
يتيح لك نظام الأذونات في Twenty التحكم بمن يمكنه الوصول إلى البيانات وتعديلها في مساحة العمل لديك. أنشئ أدوارًا، وامنح أذونات، وقم بتكوين تسجيل الدخول الأحادي (SSO) للوصول الآمن.
|
||||
|
||||
## ما الذي يتضمنه هذا القسم
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: \ا\ل\إ\ع\د\ا\د\ا\ت
|
|||
description: قم بإعداد مساحة عملك في Twenty باستخدام التكوينات الأساسية.
|
||||
---
|
||||
|
||||
|
||||
## الإعداد الأولي
|
||||
|
||||
عند إنشاء مساحة العمل لأول مرة، توجد عدة إعدادات أساسية لتكوينها.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ description: تعرّف على كيفية إنشاء العروض وإدارته
|
|||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
|
||||
## فهم العروض
|
||||
|
||||
العروض هي إعدادات محفوظة تحدد كيفية عرض بياناتك. يمكن أن يتضمن كل عرض ما يلي:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ title: سير العمل
|
|||
description: تعرّف على كيفية إنشاء عمليات الأتمتة في Twenty.
|
||||
---
|
||||
|
||||
|
||||
## أهمية تدفقات العمل
|
||||
|
||||
تم تصميم Twenty لتوفير أقصى قدر من المرونة للمستخدمين. بدلاً من إجبارك على تكييف عمليات عملك مع ميزات ثابتة ومعدة مسبقًا، تتيح تدفقات العمل لك بناء الأتمتة التي تُنشئ نظام إدارة علاقات العملاء (CRM) الأنسب لحالات الاستخدام الفريدة الخاصة بك.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Příkazy backendu
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## Užitečné příkazy
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Hlášení chyb, požadavky a pull requesty
|
||||
icon: bug
|
||||
info: Nahlašujte problémy, žádejte o nové funkce a přispívejte kódem
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Osvědčené postupy
|
||||
icon: star
|
||||
---
|
||||
|
||||
Tento dokument popisuje osvědčené postupy, které byste měli dodržovat při práci na frontend.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Architektura složek
|
||||
icon: folder-tree
|
||||
info: Podrobný pohled na naši architekturu složek
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Příkazy Frontend
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## Užitečné příkazy
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Stylová příručka
|
||||
icon: paintbrush
|
||||
---
|
||||
|
||||
Tento dokument obsahuje pravidla pro psaní kódu.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Místní nastavení
|
||||
icon: laptop-code
|
||||
description: Průvodce pro přispěvatele (nebo zvídavé vývojáře), kteří chtějí spustit Twenty lokálně.
|
||||
---
|
||||
|
||||
|
|
|
|||
77
packages/twenty-docs/l/cs/developers/contribute/commands.mdx
Normal file
77
packages/twenty-docs/l/cs/developers/contribute/commands.mdx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: Commands
|
||||
icon: terminal
|
||||
description: Useful commands for developing Twenty.
|
||||
---
|
||||
|
||||
Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting.
|
||||
|
||||
## Starting the App
|
||||
|
||||
```bash
|
||||
npx nx start twenty-front # Frontend dev server (http://localhost:3001)
|
||||
npx nx start twenty-server # Backend server (http://localhost:3000)
|
||||
npx nx run twenty-server:worker # Background worker
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
```bash
|
||||
npx nx database:reset twenty-server # Reset and seed database
|
||||
npx nx run twenty-server:database:migrate:prod # Run migrations
|
||||
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow> # Generate a migration
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
```bash
|
||||
npx nx lint:diff-with-main twenty-front # Lint changed files (fastest)
|
||||
npx nx lint:diff-with-main twenty-server
|
||||
npx nx lint twenty-front --configuration=fix # Auto-fix
|
||||
```
|
||||
|
||||
## Type Checking
|
||||
|
||||
```bash
|
||||
npx nx typecheck twenty-front
|
||||
npx nx typecheck twenty-server
|
||||
```
|
||||
|
||||
## Testování
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
npx nx test twenty-front # Jest unit tests
|
||||
npx nx storybook:build twenty-front # Build Storybook
|
||||
npx nx storybook:test twenty-front # Storybook tests
|
||||
|
||||
# Backend
|
||||
npx nx run twenty-server:test:unit # Unit tests
|
||||
npx nx run twenty-server:test:integration # Integration tests
|
||||
npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset
|
||||
|
||||
# Single file (fastest)
|
||||
npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs
|
||||
```
|
||||
|
||||
## GraphQL
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:graphql:generate # Regenerate types
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema
|
||||
```
|
||||
|
||||
## Překlady
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract # Extract strings
|
||||
npx nx run twenty-front:lingui:compile # Compile translations
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npx nx build twenty-shared # Must be built first
|
||||
npx nx build twenty-front
|
||||
npx nx build twenty-server
|
||||
```
|
||||
176
packages/twenty-docs/l/cs/developers/contribute/style-guide.mdx
Normal file
176
packages/twenty-docs/l/cs/developers/contribute/style-guide.mdx
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: Stylová příručka
|
||||
icon: paintbrush
|
||||
description: Code conventions and best practices for contributing to Twenty.
|
||||
---
|
||||
|
||||
## React
|
||||
|
||||
### Functional components only
|
||||
|
||||
Always use TSX functional components with named exports.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const MyComponent = () => {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
export default MyComponent;
|
||||
|
||||
// ✅ Good
|
||||
export function MyComponent() {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Vlastnosti
|
||||
|
||||
Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`.
|
||||
|
||||
```tsx
|
||||
type MyComponentProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||||
```
|
||||
|
||||
### No single-variable prop spreading
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
|
||||
|
||||
// ✅ Good
|
||||
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
|
||||
```
|
||||
|
||||
## Správa stavu
|
||||
|
||||
### Jotai atoms for global state
|
||||
|
||||
```tsx
|
||||
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
|
||||
export const myAtomState = createAtomState<string>({
|
||||
key: 'myAtomState',
|
||||
defaultValue: 'default value',
|
||||
});
|
||||
```
|
||||
|
||||
* Prefer atoms over prop drilling
|
||||
* Don't use `useRef` for state — use `useState` or atoms
|
||||
* Use atom families and selectors for lists
|
||||
|
||||
### Avoid unnecessary re-renders
|
||||
|
||||
* Extract `useEffect` and data fetching into sibling sidecar components
|
||||
* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect`
|
||||
* Don't use `React.memo()` — fix the root cause instead
|
||||
* Limit `useCallback` / `useMemo` usage
|
||||
|
||||
```tsx
|
||||
// ❌ Bad — useEffect in the same component causes re-renders
|
||||
export const Page = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [dep] = useAtomState(depState);
|
||||
useEffect(() => { setData(dep); }, [dep]);
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
|
||||
// ✅ Good — extract into sibling
|
||||
export const PageData = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [dep] = useAtomState(depState);
|
||||
useEffect(() => { setData(dep); }, [dep]);
|
||||
return <></>;
|
||||
};
|
||||
export const Page = () => {
|
||||
const [data] = useAtomState(dataState);
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
* **`type` over `interface`** — more flexible, easier to compose
|
||||
* **String literals over enums** — except for GraphQL codegen enums and internal library APIs
|
||||
* **No `any`** — strict TypeScript enforced
|
||||
* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`)
|
||||
* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects
|
||||
|
||||
## JavaScript
|
||||
|
||||
```tsx
|
||||
// Use nullish-coalescing (??) instead of ||
|
||||
const value = process.env.MY_VALUE ?? 'default';
|
||||
|
||||
// Use optional chaining
|
||||
onClick?.();
|
||||
```
|
||||
|
||||
## Pojmenovávání
|
||||
|
||||
* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`)
|
||||
* **Constants**: SCREAMING_SNAKE_CASE
|
||||
* **Types/Classes**: PascalCase
|
||||
* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`)
|
||||
* **Event handlers**: `handleClick` (not `onClick` for the handler function)
|
||||
* **Component props**: prefix with component name (`ButtonProps`)
|
||||
* **Styled components**: prefix with `Styled` (`StyledTitle`)
|
||||
|
||||
## Styling
|
||||
|
||||
Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const StyledButton = styled.button`
|
||||
color: #333333;
|
||||
font-size: 1rem;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
// ✅ Good
|
||||
const StyledButton = styled.button`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
```
|
||||
|
||||
## Importy
|
||||
|
||||
Use aliases instead of relative paths:
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
import { Foo } from '../../../../../testing/decorators/Foo';
|
||||
|
||||
// ✅ Good
|
||||
import { Foo } from '~/testing/decorators/Foo';
|
||||
import { Bar } from '@/modules/bar/components/Bar';
|
||||
```
|
||||
|
||||
## Folder Structure
|
||||
|
||||
```
|
||||
front
|
||||
└── modules/ # Feature modules
|
||||
│ └── module1/
|
||||
│ ├── components/
|
||||
│ ├── constants/
|
||||
│ ├── contexts/
|
||||
│ ├── graphql/ (fragments, queries, mutations)
|
||||
│ ├── hooks/
|
||||
│ ├── states/ (atoms, selectors)
|
||||
│ ├── types/
|
||||
│ └── utils/
|
||||
└── pages/ # Route-level components
|
||||
└── ui/ # Reusable UI components (display, input, feedback, ...)
|
||||
```
|
||||
|
||||
* Modules can import from other modules, but `ui/` should stay dependency-free
|
||||
* Use `internal/` subfolders for module-private code
|
||||
* Components under 300 lines, services under 500 lines
|
||||
|
|
@ -1,147 +1,55 @@
|
|||
---
|
||||
title: API
|
||||
description: Programově dotazujte a upravujte svá CRM data pomocí REST nebo GraphQL.
|
||||
icon: plug
|
||||
description: REST and GraphQL APIs generated from your workspace schema.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Twenty bylo vytvořeno s ohledem na vývojáře a nabízí výkonná API, která se přizpůsobí vašemu vlastnímu datovému modelu. Nabízíme čtyři typy API, které splňují různé potřeby integrace.
|
||||
## Schema-per-tenant APIs
|
||||
|
||||
## Přístup orientovaný na vývojáře
|
||||
There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs.
|
||||
|
||||
Twenty generuje API specificky pro váš datový model:
|
||||
Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data.
|
||||
|
||||
* **Nejsou vyžadována dlouhá ID**: Používejte v koncových bodech přímo názvy objektů a polí.
|
||||
* **Standardní a vlastní objekty jsou rovnocenně zpracovány**: Vaše vlastní objekty mají stejnou podporu API jako vestavěné.
|
||||
* **Vyhrazené koncové body**: Každý objekt a každé pole má svůj vlastní koncový bod API.
|
||||
* **Vlastní dokumentace**: Generována specificky pro datový model vašeho pracovního prostoru.
|
||||
## Two APIs
|
||||
|
||||
<Note>
|
||||
Vaše personalizovaná dokumentace k API je dostupná v **Nastavení → API & Webhooks** po vytvoření API klíče. Protože Twenty generuje API odpovídající vašemu vlastnímu datovému modelu, dokumentace je jedinečná pro váš pracovní prostor.
|
||||
</Note>
|
||||
**Core API** — `/rest/` and `/graphql/`
|
||||
|
||||
## Dva typy API
|
||||
CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations.
|
||||
|
||||
### Core API
|
||||
**Metadata API** — `/rest/metadata/` and `/metadata/`
|
||||
|
||||
Přístupné na `/rest/` nebo `/graphql/`
|
||||
Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model.
|
||||
|
||||
Pracujte se svými skutečnými **záznamy** (daty):
|
||||
Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way.
|
||||
|
||||
* Vytvářejte, čtěte, aktualizujte a mazejte osoby, společnosti, příležitosti atd.
|
||||
* Dotazujte a filtrujte data
|
||||
* Spravujte vztahy mezi záznamy.
|
||||
## Base URLs
|
||||
|
||||
### Metadata API
|
||||
|
||||
Přístupné na `/rest/metadata/` nebo `/metadata/`
|
||||
|
||||
Spravujte svůj **pracovní prostor a datový model**:
|
||||
|
||||
* Vytvářejte, upravujte nebo mazejte objekty a pole.
|
||||
* Konfigurujte nastavení pracovního prostoru.
|
||||
* Definujte vztahy mezi objekty
|
||||
|
||||
## REST vs GraphQL
|
||||
|
||||
Jak Core, tak Metadata API jsou k dispozici ve formátech REST a GraphQL:
|
||||
|
||||
| Formát | Dostupné operace |
|
||||
| ----------- | ---------------------------------------------------------------------- |
|
||||
| **REST** | CRUD, hromadné operace, operace upsert |
|
||||
| **GraphQL** | Stejné + **hromadné operace upsert**, dotazy na vztahy v jednom volání |
|
||||
|
||||
Zvolte podle svých potřeb — oba formáty přistupují ke stejným datům.
|
||||
|
||||
## Koncové body API
|
||||
|
||||
| Prostředí | Základní URL |
|
||||
| ------------------- | ------------------------- |
|
||||
| **Cloud** | `https://api.twenty.com/` |
|
||||
| **Vlastní hosting** | `https://{your-domain}/` |
|
||||
| Prostředí | Základní URL |
|
||||
| ----------- | ------------------------- |
|
||||
| Cloud | `https://api.twenty.com/` |
|
||||
| Self-Hosted | `https://{your-domain}/` |
|
||||
|
||||
## Ověření
|
||||
|
||||
Každý požadavek na API vyžaduje klíč API v hlavičce:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
### Vytvořit API klíč
|
||||
|
||||
1. Přejděte na **Nastavení → APIs & Webhooks**
|
||||
2. Klikněte na **+ Vytvořit klíč**
|
||||
3. Nakonfigurujte:
|
||||
* **Název**: Popisný název pro klíč
|
||||
* **Datum vypršení platnosti**: Kdy klíč vyprší
|
||||
4. Klikněte na **Uložit**
|
||||
5. **Zkopírujte ihned** — klíč se zobrazí pouze jednou
|
||||
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access.
|
||||
|
||||
<VimeoEmbed videoId="928786722" title="Vytvoření klíče API" />
|
||||
|
||||
<Warning>
|
||||
Váš klíč API poskytuje přístup k citlivým datům. Nesdílejte ho s nedůvěryhodnými službami. Pokud je kompromitován, okamžitě ho deaktivujte a vygenerujte nový.
|
||||
</Warning>
|
||||
For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/cs/developers/extend/oauth).
|
||||
|
||||
### Přiřaďte roli klíči API
|
||||
## Batch operations
|
||||
|
||||
Pro vyšší bezpečnost přiřaďte konkrétní roli, abyste omezili přístup:
|
||||
Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`.
|
||||
|
||||
1. Přejděte na **Nastavení → Role**
|
||||
2. Klikněte na roli, kterou chcete přiřadit
|
||||
3. Otevřete záložku **Přiřazení**
|
||||
4. V části **API Keys** klikněte na **+ Přiřadit ke klíči API**
|
||||
5. Vyberte klíč API
|
||||
## Rate limits
|
||||
|
||||
Klíč zdědí oprávnění této role. Podrobnosti viz [Oprávnění](/l/cs/user-guide/permissions-access/capabilities/permissions).
|
||||
|
||||
### Spravovat API klíče
|
||||
|
||||
**Znovu vygenerovat**: Nastavení → APIs & Webhooks → Klikněte na klíč → **Znovu vygenerovat**
|
||||
|
||||
**Smazat**: Nastavení → APIs & Webhooks → Klikněte na klíč → **Smazat**
|
||||
|
||||
## API Playground
|
||||
|
||||
Testujte svá API přímo v prohlížeči pomocí našeho vestavěného playgroundu — k dispozici pro **REST** i **GraphQL**.
|
||||
|
||||
### Přístup do Playgroundu
|
||||
|
||||
1. Přejděte na **Nastavení → APIs & Webhooks**
|
||||
2. Vytvořte klíč API (povinné)
|
||||
3. Klikněte na **REST API** nebo **GraphQL API** pro otevření playgroundu
|
||||
|
||||
### Co získáte
|
||||
|
||||
* **Interaktivní dokumentace**: Generována pro váš specifický datový model
|
||||
* **Živé testování**: Spouštějte reálná volání API vůči vašemu pracovnímu prostoru
|
||||
* **Průzkumník schématu**: Procházejte dostupné objekty, pole a vztahy
|
||||
* **Tvůrce požadavků**: Sestavujte dotazy s automatickým doplňováním
|
||||
|
||||
Playground odráží vaše vlastní objekty a pole, takže dokumentace je pro váš pracovní prostor vždy přesná.
|
||||
|
||||
## Hromadné operace
|
||||
|
||||
REST i GraphQL podporují hromadné operace:
|
||||
|
||||
* **Velikost dávky**: Až 60 záznamů na požadavek.
|
||||
* **Operace**: Vytváření, aktualizace a mazání více záznamů
|
||||
|
||||
**Funkce pouze pro GraphQL:**
|
||||
|
||||
* **Hromadný upsert**: Vytvoření nebo aktualizace v jednom volání
|
||||
* Používejte množná čísla názvů objektů (např. `CreateCompanies` místo `CreateCompany`)
|
||||
|
||||
## Limity rychlosti
|
||||
|
||||
Požadavky na API jsou omezovány, aby byla zajištěna stabilita platformy:
|
||||
|
||||
| Limit | Hodnota |
|
||||
| ------------------ | -------------------- |
|
||||
| **Požadavky** | 100 volání za minutu |
|
||||
| **Velikost dávky** | 60 záznamů na volání |
|
||||
|
||||
<Tip>
|
||||
Pro maximalizaci propustnosti používejte hromadné operace — zpracujte až 60 záznamů v jediném volání API místo odesílání jednotlivých požadavků.
|
||||
</Tip>
|
||||
| Limit | Hodnota |
|
||||
| ---------- | -------------------- |
|
||||
| Requests | 100 per minute |
|
||||
| Batch size | 60 záznamů na volání |
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,434 @@
|
|||
---
|
||||
title: CLI & Testing
|
||||
description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## Veřejné prostředky (složka `public/`)
|
||||
|
||||
Složka `public/` v kořenu vaší aplikace obsahuje statické soubory — obrázky, ikony, písma a další prostředky, které vaše aplikace potřebuje za běhu. Tyto soubory jsou automaticky zahrnuty do buildů, synchronizovány během vývojového režimu a nahrávány na server.
|
||||
|
||||
Soubory umístěné v `public/` jsou:
|
||||
|
||||
* **Veřejně přístupné** — po synchronizaci na server jsou prostředky dostupné na veřejné URL. K přístupu k nim není potřeba žádná autentizace.
|
||||
* **Dostupné ve frontendových komponentách** — použijte URL prostředků k zobrazení obrázků, ikon či jiných médií uvnitř komponent Reactu.
|
||||
* **Dostupné v logických funkcích** — odkazujte na URL prostředků v e-mailech, odpovědích API či jiné serverové logice.
|
||||
* **Používány pro metadata Marketplace** — pole `logoUrl` a `screenshots` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Tyto se zobrazují v Marketplace, když je vaše aplikace zveřejněna.
|
||||
* **Automaticky synchronizované ve vývojovém režimu** — když v `public/` přidáte, aktualizujete nebo smažete soubor, je automaticky synchronizován na server. Není potřeba restart.
|
||||
* **Zahrnuté do buildů** — `yarn twenty build` zabalí všechny veřejné prostředky do distribučního výstupu.
|
||||
|
||||
### Přístup k veřejným prostředkům pomocí `getPublicAssetUrl`
|
||||
|
||||
K získání plné URL souboru ve vaší složce `public/` použijte pomocnou funkci `getPublicAssetUrl` z `twenty-sdk`. Funguje jak v logických funkcích, tak ve frontendových komponentách.
|
||||
|
||||
**V logické funkci:**
|
||||
|
||||
```ts src/logic-functions/send-invoice.ts
|
||||
import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (): Promise<any> => {
|
||||
const logoUrl = getPublicAssetUrl('logo.png');
|
||||
const invoiceUrl = getPublicAssetUrl('templates/invoice.png');
|
||||
|
||||
// Fetch the file content (no auth required — public endpoint)
|
||||
const response = await fetch(invoiceUrl);
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
return { logoUrl, size: buffer.byteLength };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-...',
|
||||
name: 'send-invoice',
|
||||
description: 'Sends an invoice with the app logo',
|
||||
timeoutSeconds: 10,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**Ve frontendové komponentě:**
|
||||
|
||||
```tsx src/front-components/company-card.tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
export default defineFrontComponent(() => {
|
||||
const logoUrl = getPublicAssetUrl('logo.png');
|
||||
|
||||
return <img src={logoUrl} alt="App logo" />;
|
||||
});
|
||||
```
|
||||
|
||||
Argument `path` je relativní ke složce `public/` vaší aplikace. Jak `getPublicAssetUrl('logo.png')`, tak `getPublicAssetUrl('public/logo.png')` se vyhodnotí na stejnou URL — předpona `public/` je, je-li přítomna, automaticky odstraněna.
|
||||
|
||||
## Používání balíčků npm
|
||||
|
||||
Ve své aplikaci můžete nainstalovat a používat libovolný balíček npm. Logické funkce i frontendové komponenty se bundlují pomocí [esbuild](https://esbuild.github.io/), který vloží všechny závislosti přímo do výstupu — za běhu nejsou potřeba žádné `node_modules`.
|
||||
|
||||
### Instalace balíčku
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add axios
|
||||
```
|
||||
|
||||
Poté jej importujte ve svém kódu:
|
||||
|
||||
```ts src/logic-functions/fetch-data.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import axios from 'axios';
|
||||
|
||||
const handler = async (): Promise<any> => {
|
||||
const { data } = await axios.get('https://api.example.com/data');
|
||||
|
||||
return { data };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: '...',
|
||||
name: 'fetch-data',
|
||||
description: 'Fetches data from an external API',
|
||||
timeoutSeconds: 10,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Stejně to funguje i pro frontendové komponenty:
|
||||
|
||||
```tsx src/front-components/chart.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
const DateWidget = () => {
|
||||
return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'date-widget',
|
||||
component: DateWidget,
|
||||
});
|
||||
```
|
||||
|
||||
### Jak funguje bundlování
|
||||
|
||||
Krok sestavení používá esbuild k vytvoření jediného samostatného souboru pro každou logickou funkci a každou frontendovou komponentu. Všechny importované balíčky jsou vloženy přímo do bundlu.
|
||||
|
||||
**Logické funkce** běží v prostředí Node.js. Vestavěné moduly Node (`fs`, `path`, `crypto`, `http` atd.) jsou k dispozici a není je třeba instalovat.
|
||||
|
||||
**Frontendové komponenty** běží ve Web Workeru. Vestavěné moduly Node nejsou k dispozici — pouze prohlížečová API a balíčky npm, které fungují v prohlížečovém prostředí.
|
||||
|
||||
V obou prostředích jsou jako předpřipravené moduly k dispozici `twenty-client-sdk/core` a `twenty-client-sdk/metadata` — nejsou součástí bundlu, ale server je za běhu načítá.
|
||||
|
||||
## Testování vaší aplikace
|
||||
|
||||
SDK poskytuje programová rozhraní, která vám umožní z testovacího kódu aplikaci sestavit, nasadit, nainstalovat a odinstalovat. V kombinaci s [Vitest](https://vitest.dev/) a typovanými klienty API můžete psát integrační testy, které ověří, že vaše aplikace funguje end-to-end proti reálnému serveru Twenty.
|
||||
|
||||
### Nastavení
|
||||
|
||||
Vygenerovaná aplikace již obsahuje Vitest. Pokud to nastavujete ručně, nainstalujte závislosti:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add -D vitest vite-tsconfig-paths
|
||||
```
|
||||
|
||||
Vytvořte `vitest.config.ts` v kořeni vaší aplikace:
|
||||
|
||||
```ts vitest.config.ts
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
projects: ['tsconfig.spec.json'],
|
||||
ignoreConfigErrors: true,
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
testTimeout: 120_000,
|
||||
hookTimeout: 120_000,
|
||||
include: ['src/**/*.integration-test.ts'],
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:2020',
|
||||
TWENTY_API_KEY: 'your-api-key',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru:
|
||||
|
||||
```ts src/__tests__/setup-test.ts
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
|
||||
beforeAll(async () => {
|
||||
// Verify the server is running
|
||||
const response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||||
'Start the server before running integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
// Write a temporary config for the SDK
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||||
JSON.stringify({
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
}, null, 2),
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Programová rozhraní SDK
|
||||
|
||||
Subcesta `twenty-sdk/cli` exportuje funkce, které můžete volat přímo z testovacího kódu:
|
||||
|
||||
| Funkce | Popis |
|
||||
| -------------- | ----------------------------------------------------- |
|
||||
| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball |
|
||||
| `appDeploy` | Nahraje tarball na server |
|
||||
| `appInstall` | Nainstaluje aplikaci do aktivního pracovního prostoru |
|
||||
| `appUninstall` | Odinstaluje aplikaci z aktivního pracovního prostoru |
|
||||
|
||||
Každá funkce vrací objekt výsledku se `success: boolean` a buď `data`, nebo `error`.
|
||||
|
||||
### Psání integračního testu
|
||||
|
||||
Zde je kompletní příklad, který aplikaci sestaví, nasadí a nainstaluje a poté ověří, že se objeví v pracovním prostoru:
|
||||
|
||||
```ts src/__tests__/app-install.integration-test.ts
|
||||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||||
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
const APP_PATH = process.cwd();
|
||||
|
||||
describe('App installation', () => {
|
||||
beforeAll(async () => {
|
||||
const buildResult = await appBuild({
|
||||
appPath: APP_PATH,
|
||||
tarball: true,
|
||||
onProgress: (message: string) => console.log(`[build] ${message}`),
|
||||
});
|
||||
|
||||
if (!buildResult.success) {
|
||||
throw new Error(`Build failed: ${buildResult.error?.message}`);
|
||||
}
|
||||
|
||||
const deployResult = await appDeploy({
|
||||
tarballPath: buildResult.data.tarballPath!,
|
||||
onProgress: (message: string) => console.log(`[deploy] ${message}`),
|
||||
});
|
||||
|
||||
if (!deployResult.success) {
|
||||
throw new Error(`Deploy failed: ${deployResult.error?.message}`);
|
||||
}
|
||||
|
||||
const installResult = await appInstall({ appPath: APP_PATH });
|
||||
|
||||
if (!installResult.success) {
|
||||
throw new Error(`Install failed: ${installResult.error?.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await appUninstall({ appPath: APP_PATH });
|
||||
});
|
||||
|
||||
it('should find the installed app in the workspace', async () => {
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const result = await metadataClient.query({
|
||||
findManyApplications: {
|
||||
id: true,
|
||||
name: true,
|
||||
universalIdentifier: true,
|
||||
},
|
||||
});
|
||||
|
||||
const installedApp = result.findManyApplications.find(
|
||||
(app: { universalIdentifier: string }) =>
|
||||
app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
);
|
||||
|
||||
expect(installedApp).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Spuštění testů
|
||||
|
||||
Ujistěte se, že běží váš lokální server Twenty, a poté:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn test
|
||||
```
|
||||
|
||||
Nebo v režimu watch během vývoje:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn test:watch
|
||||
```
|
||||
|
||||
### Kontrola typů
|
||||
|
||||
Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty typecheck
|
||||
```
|
||||
|
||||
Spustí se `tsc --noEmit` a nahlásí se případné chyby typů.
|
||||
|
||||
## Referenční dokumentace CLI
|
||||
|
||||
Kromě `dev`, `build`, `add` a `typecheck` poskytuje CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací.
|
||||
|
||||
### Spouštění funkcí (`yarn twenty exec`)
|
||||
|
||||
Spusťte logickou funkci ručně bez vyvolání přes HTTP, cron nebo databázovou událost:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Execute by function name
|
||||
yarn twenty exec -n create-new-post-card
|
||||
|
||||
# Execute by universalIdentifier
|
||||
yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
|
||||
# Pass a JSON payload
|
||||
yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
### Zobrazení logů funkcí (`yarn twenty logs`)
|
||||
|
||||
Streamujte výstupní logy běhu logických funkcí vaší aplikace:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Stream all function logs
|
||||
yarn twenty logs
|
||||
|
||||
# Filter by function name
|
||||
yarn twenty logs -n create-new-post-card
|
||||
|
||||
# Filter by universalIdentifier
|
||||
yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
<Note>
|
||||
To je jiné než `yarn twenty server logs`, které zobrazují logy kontejneru Docker. `yarn twenty logs` zobrazuje logy spuštění funkcí vaší aplikace ze serveru Twenty.
|
||||
</Note>
|
||||
|
||||
### Odinstalace aplikace (`yarn twenty uninstall`)
|
||||
|
||||
Odeberte svou aplikaci z aktivního pracovního prostoru:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty uninstall
|
||||
|
||||
# Skip the confirmation prompt
|
||||
yarn twenty uninstall --yes
|
||||
```
|
||||
|
||||
## Správa vzdálených serverů
|
||||
|
||||
**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat.
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new remote (opens a browser for OAuth login)
|
||||
yarn twenty remote add
|
||||
|
||||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||||
yarn twenty remote add --local
|
||||
|
||||
# Add a remote non-interactively (useful for CI)
|
||||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.
|
||||
|
||||
## CI s GitHub Actions
|
||||
|
||||
Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů.
|
||||
|
||||
Workflow:
|
||||
|
||||
1. Načte váš kód (checkout).
|
||||
2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. Nainstaluje závislosti pomocí `yarn install --immutable`
|
||||
4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce
|
||||
|
||||
```yaml .github/workflows/ci.yml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
```
|
||||
|
||||
Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí efemérní server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky.
|
||||
|
||||
Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow.
|
||||
494
packages/twenty-docs/l/cs/developers/extend/apps/data-model.mdx
Normal file
494
packages/twenty-docs/l/cs/developers/extend/apps/data-model.mdx
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
---
|
||||
title: Datový model
|
||||
description: Define objects, fields, roles, and application metadata with the Twenty SDK.
|
||||
icon: database
|
||||
---
|
||||
|
||||
The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. Abyste umožnili SDK detekovat vaše entity, musíte použít `export default defineEntity({...})`. Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
|
||||
|
||||
<Note>
|
||||
**Uspořádání souborů je na vás.**
|
||||
Detekce entit je založená na AST — SDK najde volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází. Seskupování souborů podle typu (např. `logic-functions/`, `roles/`) je pouze konvence, nikoli požadavek.
|
||||
</Note>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineRole" description="Konfigurace oprávnění rolí a přístupu k objektům">
|
||||
|
||||
Role zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru.
|
||||
|
||||
```ts restricted-company-role.ts
|
||||
import {
|
||||
defineRole,
|
||||
PermissionFlag,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk/define';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
|
||||
label: 'My new role',
|
||||
description: 'A role that can be used in your workspace',
|
||||
canReadAllObjectRecords: false,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [
|
||||
{
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: false,
|
||||
canDestroyObjectRecords: false,
|
||||
},
|
||||
],
|
||||
fieldPermissions: [
|
||||
{
|
||||
objectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
|
||||
canReadFieldValue: false,
|
||||
canUpdateFieldValue: false,
|
||||
},
|
||||
],
|
||||
permissionFlags: [PermissionFlag.APPLICATIONS],
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineApplication" description="Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci)">
|
||||
|
||||
Každá aplikace musí mít právě jedno volání `defineApplication`, které popisuje:
|
||||
|
||||
* **Identita**: identifikátory, zobrazovaný název a popis.
|
||||
* **Oprávnění**: jakou roli používají její funkce a frontendové komponenty.
|
||||
* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí.
|
||||
* **(Volitelné) předinstalační / postinstalační funkce**: logické funkce, které se spouštějí před nebo po instalaci.
|
||||
|
||||
```ts src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Poznámky:
|
||||
* Pole `universalIdentifier` jsou deterministické identifikátory, které vlastníte. Vygenerujte je jednou a zachovejte je stabilní napříč synchronizacemi.
|
||||
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce a frontendové komponenty (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` musí odkazovat na roli definovanou pomocí `defineRole()` (viz výše).
|
||||
* Předinstalační a postinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`.
|
||||
|
||||
#### Metadata tržiště
|
||||
|
||||
Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje v tržišti:
|
||||
|
||||
| Pole | Popis |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
|
||||
| `author` | Jméno autora nebo název společnosti |
|
||||
| `category` | Kategorie aplikace pro filtrování v tržišti |
|
||||
| `logoUrl` | Cesta k logu vaší aplikace (např. `public/logo.png`) |
|
||||
| `screenshots` | Pole cest ke snímkům obrazovky (např. `public/screenshot-1.png`) |
|
||||
| `aboutDescription` | Delší popis v Markdownu pro kartu "O aplikaci". Pokud je vynecháno, tržiště použije `README.md` balíčku z npm |
|
||||
| `websiteUrl` | Odkaz na váš web |
|
||||
| `termsUrl` | Odkaz na podmínky služby |
|
||||
| `emailSupport` | E-mailová adresa podpory |
|
||||
| `issueReportUrl` | Odkaz na nástroj pro sledování problémů |
|
||||
|
||||
#### Role a oprávnění
|
||||
|
||||
Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi a frontendovými komponentami vaší aplikace. Podrobnosti viz výše u `defineRole`.
|
||||
|
||||
* Běhový token vložený jako `TWENTY_APP_ACCESS_TOKEN` je odvozen z této role.
|
||||
* Typovaný klient bude omezen oprávněními udělenými této roli.
|
||||
* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují.
|
||||
|
||||
##### Výchozí role funkce
|
||||
|
||||
Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role:
|
||||
|
||||
```ts src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Default function role',
|
||||
description: 'Default role for function Twenty client',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToApiKeys: false,
|
||||
objectPermissions: [],
|
||||
fieldPermissions: [],
|
||||
permissionFlags: [],
|
||||
});
|
||||
```
|
||||
|
||||
Na `universalIdentifier` této role se v `application-config.ts` odkazuje jako na `defaultRoleUniversalIdentifier`:
|
||||
|
||||
* **\*.role.ts** definuje, co daná role může dělat.
|
||||
* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění.
|
||||
|
||||
Poznámky:
|
||||
* Začněte vygenerovanou rolí a postupně ji omezujte podle principu nejmenších oprávnění.
|
||||
* Nahraďte `objectPermissions` a `fieldPermissions` objekty a poli, které vaše funkce skutečně potřebují.
|
||||
* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší.
|
||||
* Podívejte se na funkční příklad: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineObject" description="Definice vlastních objektů s poli">
|
||||
|
||||
Vlastní objekty popisují jak schéma, tak chování záznamů ve vašem pracovním prostoru. K definování objektů s vestavěnou validací použijte `defineObject()`:
|
||||
|
||||
```ts postCard.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,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Použijte `defineObject()` pro vestavěnou validaci a lepší podporu v IDE.
|
||||
* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními.
|
||||
* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`.
|
||||
* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí.
|
||||
* Nové objekty můžete vygenerovat pomocí `yarn twenty add`, který vás provede pojmenováním, poli a vztahy.
|
||||
|
||||
<Note>
|
||||
**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole
|
||||
jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`.
|
||||
Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole.
|
||||
Výchozí pole můžete přepsat definováním pole se stejným názvem v poli `fields`,
|
||||
ale to se nedoporučuje.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineField — Standardní pole" description="Rozšiřte existující objekty o další pole">
|
||||
|
||||
Pomocí `defineField()` přidejte pole k objektům, které nevlastníte — například ke standardním objektům Twenty (Person, Company atd.). nebo k objektům z jiných aplikací. Na rozdíl od inline polí v `defineObject()` vyžadují samostatná pole `objectUniversalIdentifier` k určení, který objekt rozšiřují:
|
||||
|
||||
```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' },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* `objectUniversalIdentifier` identifikuje cílový objekt. Pro standardní objekty použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportovaný z `twenty-sdk`.
|
||||
* Při definování polí inline v `defineObject()` `objectUniversalIdentifier` nepotřebujete — dědí se z nadřazeného objektu.
|
||||
* `defineField()` je jediný způsob, jak přidat pole k objektům, které jste nevytvořili pomocí `defineObject()`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineField — Relační pole" description="Propojte objekty obousměrnými relacemi">
|
||||
|
||||
Relace propojují objekty. Ve Twenty jsou relace vždy obousměrné — definujete obě strany a každá strana odkazuje na tu druhou.
|
||||
|
||||
Existují dva typy relací:
|
||||
|
||||
| Typ vztahu | Popis | Má cizí klíč? |
|
||||
| ------------- | --------------------------------------------------------------------- | ---------------------- |
|
||||
| `MANY_TO_ONE` | Mnoho záznamů tohoto objektu ukazuje na jeden záznam cílového objektu | Ano (`joinColumnName`) |
|
||||
| `ONE_TO_MANY` | Jeden záznam tohoto objektu má mnoho záznamů cílového objektu | Ne (inverzní strana) |
|
||||
|
||||
#### Jak fungují relace
|
||||
|
||||
Každá relace vyžaduje dvě pole, která na sebe vzájemně odkazují:
|
||||
|
||||
1. Strana MANY_TO_ONE — je na objektu, který drží cizí klíč
|
||||
2. Strana ONE_TO_MANY — je na objektu, který vlastní kolekci
|
||||
|
||||
Obě pole používají `FieldType.RELATION` a vzájemně se odkazují prostřednictvím `relationTargetFieldMetadataUniversalIdentifier`.
|
||||
|
||||
#### Příklad: Pohlednice má mnoho příjemců
|
||||
|
||||
Předpokládejme, že `PostCard` lze odeslat mnoha záznamům `PostCardRecipient`. Každý příjemce náleží přesně jedné pohlednici.
|
||||
|
||||
**Krok 1: Definujte stranu ONE_TO_MANY na PostCard** (strana "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,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Krok 2: Definujte stranu MANY_TO_ONE na PostCardRecipient** (strana "many" — drží cizí klíč):
|
||||
|
||||
```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>
|
||||
**Cyklické importy:** Obě relační pole odkazují na `universalIdentifier` toho druhého. Abyste předešli problémům s cyklickými importy, exportujte ID polí jako pojmenované konstanty z každého souboru a v druhém souboru je importujte. Build systém je vyřeší v době kompilace.
|
||||
</Note>
|
||||
|
||||
#### Vazby na standardní objekty
|
||||
|
||||
Chcete-li vytvořit relaci s vestavěným objektem Twenty (Person, Company atd.), použijte `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',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Vlastnosti relačních polí
|
||||
|
||||
| Vlastnost | Povinné | Popis |
|
||||
| ------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `type` | Ano | Musí být `FieldType.RELATION` |
|
||||
| `relationTargetObjectMetadataUniversalIdentifier` | Ano | `universalIdentifier` cílového objektu |
|
||||
| `relationTargetFieldMetadataUniversalIdentifier` | Ano | `universalIdentifier` odpovídajícího pole na cílovém objektu |
|
||||
| `universalSettings.relationType` | Ano | `RelationType.MANY_TO_ONE` nebo `RelationType.ONE_TO_MANY` |
|
||||
| `universalSettings.onDelete` | Pouze MANY_TO_ONE | Co se stane, když je smazán odkazovaný záznam: `CASCADE`, `SET_NULL`, `RESTRICT` nebo `NO_ACTION` |
|
||||
| `universalSettings.joinColumnName` | Pouze MANY_TO_ONE | Název databázového sloupce pro cizí klíč (např. `postCardId`) |
|
||||
|
||||
#### Vložená relační pole v defineObject
|
||||
|
||||
Relační pole můžete také definovat přímo uvnitř `defineObject()`. V takovém případě vynechejte `objectUniversalIdentifier` — dědí se z nadřazeného objektu:
|
||||
|
||||
```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
|
||||
],
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Generování entit pomocí `yarn twenty add`
|
||||
|
||||
Místo ručního vytváření souborů entit můžete použít interaktivní generátor:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add
|
||||
```
|
||||
|
||||
Požádá vás o výběr typu entity a provede vás požadovanými poli. Vygeneruje soubor připravený k použití se stabilním `universalIdentifier` a správným voláním `defineEntity()`.
|
||||
|
||||
Můžete také předat typ entity přímo a přeskočit první dotaz:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add object
|
||||
yarn twenty add logicFunction
|
||||
yarn twenty add frontComponent
|
||||
```
|
||||
|
||||
### Dostupné typy entit
|
||||
|
||||
| Typ entity | Příkaz | Vygenerovaný soubor |
|
||||
| ------------------------- | ------------------------------------ | ------------------------------------------------------- |
|
||||
| Objekt | `yarn twenty add object` | `src/objects/\<name>.ts` |
|
||||
| Pole | `yarn twenty add field` | `src/fields/\<name>.ts` |
|
||||
| Logická funkce | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
|
||||
| Frontendová komponenta | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
|
||||
| Role | `yarn twenty add role` | `src/roles/\<name>.ts` |
|
||||
| Dovednost | `yarn twenty add skill` | `src/skills/\<name>.ts` |
|
||||
| Agent | `yarn twenty add agent` | `src/agents/\<name>.ts` |
|
||||
| Pohled | `yarn twenty add view` | `src/views/\<name>.ts` |
|
||||
| Položka navigační nabídky | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
|
||||
| Rozvržení stránky | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
|
||||
|
||||
### Co generátor vytváří
|
||||
|
||||
Každý typ entity má vlastní šablonu. Například `yarn twenty add object` se zeptá na:
|
||||
|
||||
1. **Název (jednotné číslo)** — např. `invoice`
|
||||
2. **Název (množné číslo)** — např. `invoices`
|
||||
3. **Štítek (jednotné číslo)** — automaticky doplněn z názvu (např. `Invoice`)
|
||||
4. **Štítek (množné číslo)** — automaticky doplněn (např. `Invoices`)
|
||||
5. **Vytvořit zobrazení a položku navigace?** — pokud odpovíte ano, generátor také vytvoří odpovídající zobrazení a odkaz v postranním panelu pro nový objekt.
|
||||
|
||||
Ostatní typy entit mají jednodušší dotazy — většinou se ptají pouze na název.
|
||||
|
||||
Typ entity `field` je podrobnější: ptá se na název pole, štítek, typ (ze seznamu všech dostupných typů polí jako `TEXT`, `NUMBER`, `SELECT`, `RELATION` atd.) a `universalIdentifier` cílového objektu.
|
||||
|
||||
### Vlastní výstupní cesta
|
||||
|
||||
Pomocí příznaku `--path` umístíte vygenerovaný soubor do vlastního umístění:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add logicFunction --path src/custom-folder
|
||||
```
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
---
|
||||
title: Frontendové komponenty
|
||||
description: Build React components that render inside Twenty's UI with sandboxed isolation.
|
||||
icon: window-maximize
|
||||
---
|
||||
|
||||
Frontendové komponenty jsou React komponenty, které se vykreslují přímo v uživatelském rozhraní Twenty. Běží v **izolovaném Web Workeru** s využitím Remote DOM — váš kód je sandboxovaný, ale vykresluje se nativně na stránce, nikoli v iframu.
|
||||
|
||||
## Kde lze použít frontendové komponenty
|
||||
|
||||
Frontendové komponenty se mohou vykreslovat na dvou místech v rámci Twenty:
|
||||
|
||||
* **Postranní panel** — Frontendové komponenty, které nejsou headless, se otevírají v pravém postranním panelu. Toto je výchozí chování, když je frontendová komponenta vyvolána z příkazového menu.
|
||||
* **Widgety (nástěnky a stránky záznamů)** — Frontendové komponenty lze vkládat jako widgety do rozložení stránek. Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget frontendové komponenty.
|
||||
|
||||
## Základní příklad
|
||||
|
||||
Nejrychlejší způsob, jak vidět frontendovou komponentu v akci, je zaregistrovat ji jako **příkaz**. Přidáním pole `command` s `isPinned: true` se zobrazí jako tlačítko rychlé akce v pravém horním rohu stránky — není potřeba žádné rozvržení stránky:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
|
||||
const HelloWorld = () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
||||
<h1>Hello from my app!</h1>
|
||||
<p>This component renders inside Twenty.</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
|
||||
name: 'hello-world',
|
||||
description: 'A simple front component',
|
||||
component: HelloWorld,
|
||||
command: {
|
||||
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
|
||||
shortLabel: 'Hello',
|
||||
label: 'Hello World',
|
||||
icon: 'IconBolt',
|
||||
isPinned: true,
|
||||
availabilityType: 'GLOBAL',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty dev --once`) se rychlá akce zobrazí v pravém horním rohu stránky:
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Tlačítko rychlé akce v pravém horním rohu" />
|
||||
</div>
|
||||
|
||||
Kliknutím na něj vykreslíte komponentu přímo ve stránce.
|
||||
|
||||
## Konfigurační pole
|
||||
|
||||
| Pole | Povinné | Popis |
|
||||
| --------------------- | ------- | ------------------------------------------------------------------------------------ |
|
||||
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro tuto komponentu |
|
||||
| `component` | Ano | Funkce komponenty React |
|
||||
| `name` | Ne | Zobrazovaný název |
|
||||
| `description` | Ne | Popis toho, co komponenta dělá |
|
||||
| `isHeadless` | Ne | Nastavte na `true`, pokud komponenta nemá viditelné UI (viz níže) |
|
||||
| `command` | Ne | Zaregistrujte komponentu jako příkaz (viz [možnosti příkazu](#command-options) níže) |
|
||||
|
||||
## Umístění frontendové komponenty na stránku
|
||||
|
||||
Mimo příkazy můžete frontendovou komponentu vložit přímo na stránku záznamu přidáním jako widget v **rozvržení stránky**. Podrobnosti viz sekce [definePageLayout](/l/cs/developers/extend/apps/skills-and-agents#definepagelayout).
|
||||
|
||||
## Headless vs. ne-headless
|
||||
|
||||
Front-endové komponenty existují ve dvou režimech vykreslování řízených volbou `isHeadless`:
|
||||
|
||||
**Ne-headless (výchozí)** — Komponenta vykreslí viditelné uživatelské rozhraní. Po vyvolání z menu příkazů se otevře v postranním panelu. Toto je výchozí chování, když je `isHeadless` `false` nebo když tato volba není uvedena.
|
||||
|
||||
**Headless (`isHeadless: true`)** — Komponenta se neviditelně inicializuje na pozadí. Neotevírá postranní panel. Headless komponenty jsou určené pro akce, které provedou logiku a poté se odpojí — například spuštění asynchronního úkolu, navigaci na stránku nebo zobrazení potvrzovacího modálního okna. Přirozeně se hodí ke komponentám SDK Command popsaným níže.
|
||||
|
||||
```tsx src/front-components/sync-tracker.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const SyncTracker = () => {
|
||||
const recordId = useRecordId();
|
||||
|
||||
useEffect(() => {
|
||||
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
|
||||
}, [recordId]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'sync-tracker',
|
||||
description: 'Tracks record views silently',
|
||||
isHeadless: true,
|
||||
component: SyncTracker,
|
||||
});
|
||||
```
|
||||
|
||||
Protože komponenta vrací `null`, Twenty přeskočí vykreslení kontejneru — v rozvržení se neobjeví žádné prázdné místo. Komponenta má však stále přístup ke všem hookům a API komunikace s hostitelem.
|
||||
|
||||
## Komponenty SDK Command
|
||||
|
||||
Balíček `twenty-sdk` poskytuje čtyři pomocné komponenty Command navržené pro headless front-endové komponenty. Každá komponenta při připojení provede akci, chyby zpracuje zobrazením oznámení ve snackbaru a po dokončení automaticky odpojí front-endovou komponentu.
|
||||
|
||||
Importujte je z `twenty-sdk/command`:
|
||||
|
||||
* **`Command`** — Spustí asynchronní callback přes prop `execute`.
|
||||
* **`CommandLink`** — Naviguje na cestu v aplikaci. Props: `to`, `params`, `queryParams`, `options`.
|
||||
* **`CommandModal`** — Otevře potvrzovací modální okno. Pokud uživatel potvrdí, provede callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
|
||||
* **`CommandOpenSidePanelPage`** — Otevře konkrétní stránku postranního panelu. Props: `page`, `pageTitle`, `pageIcon`.
|
||||
|
||||
Zde je kompletní příklad headless front-endové komponenty, která pomocí `Command` spouští akci z menu příkazů:
|
||||
|
||||
```tsx src/front-components/run-action.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { Command } from 'twenty-sdk/command';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
const RunAction = () => {
|
||||
const execute = async () => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
await client.mutation({
|
||||
createTask: {
|
||||
__args: { data: { title: 'Created by my app' } },
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return <Command execute={execute} />;
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
|
||||
name: 'run-action',
|
||||
description: 'Creates a task from the command menu',
|
||||
component: RunAction,
|
||||
isHeadless: true,
|
||||
command: {
|
||||
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
|
||||
label: 'Run my action',
|
||||
icon: 'IconPlayerPlay',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
A příklad s použitím `CommandModal` k vyžádání potvrzení před provedením:
|
||||
|
||||
```tsx src/front-components/delete-draft.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { CommandModal } from 'twenty-sdk/command';
|
||||
|
||||
const DeleteDraft = () => {
|
||||
const execute = async () => {
|
||||
// perform the deletion
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandModal
|
||||
title="Delete draft?"
|
||||
subtitle="This action cannot be undone."
|
||||
execute={execute}
|
||||
confirmButtonText="Delete"
|
||||
confirmButtonAccent="danger"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
|
||||
name: 'delete-draft',
|
||||
description: 'Deletes a draft with confirmation',
|
||||
component: DeleteDraft,
|
||||
isHeadless: true,
|
||||
command: {
|
||||
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
|
||||
label: 'Delete draft',
|
||||
icon: 'IconTrash',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Přístup k běhovému kontextu
|
||||
|
||||
Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli, záznamu a instanci komponenty:
|
||||
|
||||
```tsx src/front-components/record-info.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
useUserId,
|
||||
useRecordId,
|
||||
useFrontComponentId,
|
||||
} from 'twenty-sdk/front-component';
|
||||
|
||||
const RecordInfo = () => {
|
||||
const userId = useUserId();
|
||||
const recordId = useRecordId();
|
||||
const componentId = useFrontComponentId();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>User: {userId}</p>
|
||||
<p>Record: {recordId ?? 'No record context'}</p>
|
||||
<p>Component: {componentId}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
|
||||
name: 'record-info',
|
||||
component: RecordInfo,
|
||||
});
|
||||
```
|
||||
|
||||
Dostupné hooky:
|
||||
|
||||
| Hook | Vrací | Popis |
|
||||
| --------------------------------------------- | -------------------- | ------------------------------------------------------------ |
|
||||
| `useUserId()` | `string` nebo `null` | ID aktuálního uživatele |
|
||||
| `useRecordId()` | `string` nebo `null` | ID aktuálního záznamu (pokud je umístěna na stránce záznamu) |
|
||||
| `useFrontComponentId()` | `string` | ID této instance komponenty |
|
||||
| `useFrontComponentExecutionContext(selector)` | různé | Přístup k úplnému kontextu běhu pomocí selektorové funkce |
|
||||
|
||||
## API komunikace s hostitelem
|
||||
|
||||
Frontendové komponenty mohou pomocí funkcí z `twenty-sdk` vyvolávat navigaci, modály a oznámení:
|
||||
|
||||
| Funkce | Popis |
|
||||
| ----------------------------------------------- | ------------------------------ |
|
||||
| `navigate(to, params?, queryParams?, options?)` | Přejít na stránku v aplikaci |
|
||||
| `openSidePanelPage(params)` | Otevřít postranní panel |
|
||||
| `closeSidePanel()` | Zavřít postranní panel |
|
||||
| `openCommandConfirmationModal(params)` | Zobrazit potvrzovací dialog |
|
||||
| `enqueueSnackbar(params)` | Zobrazit oznámení typu toast |
|
||||
| `unmountFrontComponent()` | Odpojit komponentu |
|
||||
| `updateProgress(progress)` | Aktualizovat indikátor průběhu |
|
||||
|
||||
Zde je příklad, který používá hostitelské API k zobrazení snackbaru a zavření postranního panelu po dokončení akce:
|
||||
|
||||
```tsx src/front-components/archive-record.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { useRecordId } from 'twenty-sdk/front-component';
|
||||
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
const ArchiveRecord = () => {
|
||||
const recordId = useRecordId();
|
||||
|
||||
const handleArchive = async () => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
await client.mutation({
|
||||
updateTask: {
|
||||
__args: { id: recordId, data: { status: 'ARCHIVED' } },
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
await enqueueSnackbar({
|
||||
message: 'Record archived',
|
||||
variant: 'success',
|
||||
});
|
||||
|
||||
await closeSidePanel();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<p>Archive this record?</p>
|
||||
<button onClick={handleArchive}>Archive</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
|
||||
name: 'archive-record',
|
||||
description: 'Archives the current record',
|
||||
component: ArchiveRecord,
|
||||
});
|
||||
```
|
||||
|
||||
## Možnosti příkazu
|
||||
|
||||
Přidání pole `command` do `defineFrontComponent` zaregistruje komponentu v příkazovém menu (Cmd+K). Pokud je `isPinned` nastaveno na `true`, zobrazí se také jako tlačítko rychlé akce v pravém horním rohu stránky.
|
||||
|
||||
| Pole | Povinné | Popis |
|
||||
| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz |
|
||||
| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) |
|
||||
| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce |
|
||||
| `icon` | Ne | Název ikony zobrazený vedle popisku (např. `'IconBolt'`, `'IconSend'`) |
|
||||
| `isPinned` | Ne | Pokud je `true`, zobrazí příkaz jako tlačítko rychlé akce v pravém horním rohu stránky |
|
||||
| `availabilityType` | Ne | Určuje, kde se příkaz zobrazuje: `'GLOBAL'` (vždy dostupné), `'RECORD_SELECTION'` (pouze když jsou vybrány záznamy) nebo `'FALLBACK'` (zobrazeno, když neodpovídají žádné jiné příkazy) |
|
||||
| `availabilityObjectUniversalIdentifier` | Ne | Omezí příkaz na stránky konkrétního typu objektu (např. pouze u záznamů Company) |
|
||||
| `conditionalAvailabilityExpression` | Ne | Logický výraz pro dynamické řízení, zda je příkaz viditelný (viz níže) |
|
||||
|
||||
## Výrazy podmíněné dostupnosti
|
||||
|
||||
Pole `conditionalAvailabilityExpression` vám umožní řídit viditelnost příkazu na základě aktuálního kontextu stránky. Pro sestavení výrazů importujte typované proměnné a operátory z `twenty-sdk`:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
pageType,
|
||||
numberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
everyEquals,
|
||||
isDefined,
|
||||
} from 'twenty-sdk/front-component';
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'bulk-action',
|
||||
component: BulkAction,
|
||||
command: {
|
||||
universalIdentifier: '...',
|
||||
label: 'Bulk Update',
|
||||
availabilityType: 'RECORD_SELECTION',
|
||||
conditionalAvailabilityExpression: everyEquals(
|
||||
objectPermissions,
|
||||
'canUpdateObjectRecords',
|
||||
true,
|
||||
),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Kontextové proměnné** — reprezentují aktuální stav stránky:
|
||||
|
||||
| Proměnná | Typ | Popis |
|
||||
| ------------------------------ | --------- | -------------------------------------------------------------------- |
|
||||
| `pageType` | `string` | Aktuální typ stránky (např. `'RecordIndexPage'`, `'RecordShowPage'`) |
|
||||
| `isInSidePanel` | `boolean` | Zda je komponenta vykreslena v postranním panelu |
|
||||
| `numberOfSelectedRecords` | `number` | Počet aktuálně vybraných záznamů |
|
||||
| `isSelectAll` | `boolean` | Zda je aktivní "vybrat vše" |
|
||||
| `selectedRecords` | `array` | Vybrané objekty záznamů |
|
||||
| `favoriteRecordIds` | `array` | ID oblíbených záznamů |
|
||||
| `objectPermissions` | `object` | Oprávnění pro aktuální typ objektu |
|
||||
| `targetObjectReadPermissions` | `object` | Oprávnění ke čtení pro cílový objekt |
|
||||
| `targetObjectWritePermissions` | `object` | Oprávnění k zápisu pro cílový objekt |
|
||||
| `featureFlags` | `object` | Aktivní příznaky funkcí |
|
||||
| `objectMetadataItem` | `object` | Metadata aktuálního typu objektu |
|
||||
| `hasAnySoftDeleteFilterOnView` | `boolean` | Zda má aktuální zobrazení filtr soft-delete |
|
||||
|
||||
**Operátory** — kombinují proměnné do logických výrazů:
|
||||
|
||||
| Operátor | Popis |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `isDefined(value)` | `true`, pokud hodnota není null/undefined |
|
||||
| `isNonEmptyString(value)` | `true`, pokud je hodnota neprázdný řetězec |
|
||||
| `includes(array, value)` | `true`, pokud pole obsahuje danou hodnotu |
|
||||
| `includesEvery(array, prop, value)` | `true`, pokud vlastnost každé položky zahrnuje danou hodnotu |
|
||||
| `every(array, prop)` | `true`, pokud je vlastnost u každé položky pravdivá (truthy) |
|
||||
| `everyDefined(array, prop)` | `true`, pokud je vlastnost definována u každé položky |
|
||||
| `everyEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě u každé položky |
|
||||
| `some(array, prop)` | `true`, pokud je vlastnost pravdivá (truthy) alespoň u jedné položky |
|
||||
| `someDefined(array, prop)` | `true`, pokud je vlastnost definována alespoň u jedné položky |
|
||||
| `someEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě alespoň u jedné položky |
|
||||
| `someNonEmptyString(array, prop)` | `true`, pokud má vlastnost alespoň u jedné položky hodnotu neprázdného řetězce |
|
||||
| `none(array, prop)` | `true`, pokud je vlastnost u všech položek nepravdivá (falsy) |
|
||||
| `noneDefined(array, prop)` | `true`, pokud je vlastnost u všech položek nedefinovaná |
|
||||
| `noneEquals(array, prop, value)` | `true`, pokud se vlastnost nerovná hodnotě u žádné položky |
|
||||
|
||||
## Veřejné soubory
|
||||
|
||||
Frontendové komponenty mohou přistupovat k souborům ze složky aplikace `public/` pomocí `getPublicAssetUrl`:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'logo',
|
||||
component: Logo,
|
||||
});
|
||||
```
|
||||
|
||||
Podrobnosti viz [sekci veřejných souborů](/l/cs/developers/extend/apps/cli-and-testing#public-assets-public-folder).
|
||||
|
||||
## Styling
|
||||
|
||||
Frontendové komponenty podporují více přístupů ke stylování. Můžete použít:
|
||||
|
||||
* **Inline styly** — `style={{ color: 'red' }}`
|
||||
* **Komponenty Twenty UI** — import z `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar a další)
|
||||
* **Emotion** — CSS-in-JS s `@emotion/react`
|
||||
* **Styled-components** — vzory `styled.div`
|
||||
* **Tailwind CSS** — utilitní třídy
|
||||
* **Jakákoli CSS-in-JS knihovna** kompatibilní s Reactem
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { Button, Tag, Status } from 'twenty-sdk/ui';
|
||||
|
||||
const StyledWidget = () => {
|
||||
return (
|
||||
<div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
|
||||
<Button title="Click me" onClick={() => alert('Clicked!')} />
|
||||
<Tag text="Active" color="green" />
|
||||
<Status color="green" text="Online" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
|
||||
name: 'styled-widget',
|
||||
component: StyledWidget,
|
||||
});
|
||||
```
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: Začínáme
|
||||
icon: rocket
|
||||
description: Vytvořte svou první aplikaci Twenty během několika minut.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
## Co jsou aplikace?
|
||||
|
||||
Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, frontendové komponenty, AI schopnosti a další — vše je spravováno jako kód. Místo konfigurace všeho přes uživatelské rozhraní definujete v TypeScriptu svůj datový model a logiku a nasadíte je do jednoho nebo více pracovních prostorů.
|
||||
|
|
|
|||
131
packages/twenty-docs/l/cs/developers/extend/apps/layout.mdx
Normal file
131
packages/twenty-docs/l/cs/developers/extend/apps/layout.mdx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
title: Rozvržení
|
||||
description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty.
|
||||
icon: table-columns
|
||||
---
|
||||
|
||||
Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged.
|
||||
|
||||
## Layout concepts
|
||||
|
||||
| Concept | What it controls | Entita |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- |
|
||||
| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` |
|
||||
| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` |
|
||||
| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` |
|
||||
|
||||
Views, navigation items, and page layouts reference each other by `universalIdentifier`:
|
||||
|
||||
* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view.
|
||||
* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/cs/developers/extend/apps/front-components) inside its tabs as widgets.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineView" description="Definujte uložená zobrazení pro objekty">
|
||||
|
||||
Zobrazení jsou uložené konfigurace toho, jak se zobrazují záznamy objektu — včetně toho, která pole jsou viditelná, jejich pořadí a jaké filtry či seskupení jsou použity. Pomocí `defineView()` můžete k aplikaci přidat předkonfigurovaná zobrazení:
|
||||
|
||||
```ts src/views/example-view.ts
|
||||
import { defineView, ViewKey } from 'twenty-sdk/define';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||||
import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'All example items',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconList',
|
||||
key: ViewKey.INDEX,
|
||||
position: 0,
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
|
||||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* `objectUniversalIdentifier` určuje, na který objekt se toto zobrazení vztahuje.
|
||||
* `key` určuje typ zobrazení (např. `ViewKey.INDEX` pro hlavní seznam).
|
||||
* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`.
|
||||
* Pro pokročilejší konfigurace můžete definovat také `filters`, `filterGroups`, `groups` a `fieldGroups`.
|
||||
* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineNavigationMenuItem" description="Definujte odkazy postranní navigace">
|
||||
|
||||
Položky navigační nabídky přidávají vlastní položky do postranního panelu pracovního prostoru. Použijte `defineNavigationMenuItem()` k odkazování na zobrazení, externí URL nebo objekty:
|
||||
|
||||
```ts src/navigation-menu-items/example-navigation-menu-item.ts
|
||||
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
|
||||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c',
|
||||
name: 'example-navigation-menu-item',
|
||||
icon: 'IconList',
|
||||
color: 'blue',
|
||||
position: 0,
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* `type` určuje, na co položka menu odkazuje: `NavigationMenuItemType.VIEW` pro uložené zobrazení nebo `NavigationMenuItemType.LINK` pro externí URL.
|
||||
* Pro odkazy na zobrazení nastavte `viewUniversalIdentifier`. Pro externí odkazy nastavte `link`.
|
||||
* `position` určuje pořadí v postranním panelu.
|
||||
* `icon` a `color` (volitelné) upravují vzhled.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePageLayout" description="Definujte vlastní rozvržení stránek pro zobrazení záznamů">
|
||||
|
||||
Rozvržení stránek vám umožní přizpůsobit vzhled stránky s detailem záznamu — které karty se zobrazí, jaké widgety jsou uvnitř každé karty a jak jsou uspořádány. Pomocí `definePageLayout()` můžete k aplikaci přidat vlastní rozvržení:
|
||||
|
||||
```ts src/page-layouts/example-record-page-layout.ts
|
||||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
|
||||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||||
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
|
||||
|
||||
export default definePageLayout({
|
||||
universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
|
||||
name: 'Example Record Page',
|
||||
type: 'RECORD_PAGE',
|
||||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
tabs: [
|
||||
{
|
||||
universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
|
||||
title: 'Hello World',
|
||||
position: 50,
|
||||
icon: 'IconWorld',
|
||||
layoutMode: PageLayoutTabLayoutMode.CANVAS,
|
||||
widgets: [
|
||||
{
|
||||
universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
|
||||
title: 'Hello World',
|
||||
type: 'FRONT_COMPONENT',
|
||||
configuration: {
|
||||
configurationType: 'FRONT_COMPONENT',
|
||||
frontComponentUniversalIdentifier:
|
||||
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* `type` je obvykle `'RECORD_PAGE'` pro úpravu detailního zobrazení konkrétního objektu.
|
||||
* `objectUniversalIdentifier` určuje, na který objekt se toto rozvržení vztahuje.
|
||||
* Každá `tab` definuje sekci stránky s `title`, `position` a `layoutMode` (`CANVAS` pro volné rozvržení).
|
||||
* Každý `widget` uvnitř karty může vykreslit frontendovou komponentu, seznam relací nebo jiné vestavěné typy widgetů.
|
||||
* `position` na kartách určuje jejich pořadí. Použijte vyšší hodnoty (např. 50) pro umístění vlastních karet za vestavěné.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
|
@ -0,0 +1,560 @@
|
|||
---
|
||||
title: Logické funkce
|
||||
description: Define server-side TypeScript functions with HTTP, cron, and database event triggers.
|
||||
icon: bolt
|
||||
---
|
||||
|
||||
Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineLogicFunction" description="Definujte logické funkce a jejich spouštěče">
|
||||
|
||||
Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči.
|
||||
|
||||
```ts src/logic-functions/createPostCard.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define';
|
||||
import { CoreApiClient, type Person } from 'twenty-client-sdk/core';
|
||||
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new CoreApiClient();
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
: 'Hello world';
|
||||
|
||||
const result = await client.mutation({
|
||||
createPostCard: {
|
||||
__args: { data: { name } },
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/post-card/create',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
/*databaseEventTriggerSettings: {
|
||||
eventName: 'people.created',
|
||||
},*/
|
||||
/*cronTriggerSettings: {
|
||||
pattern: '0 0 1 1 *',
|
||||
},*/
|
||||
});
|
||||
```
|
||||
|
||||
Dostupné typy spouštěčů:
|
||||
* **httpRoute**: Zpřístupní vaši funkci na HTTP cestě a metodě **pod koncovým bodem `/s/`**:
|
||||
> např. `path: '/post-card/create'` je volatelné na `https://your-twenty-server.com/s/post-card/create`
|
||||
* **cron**: Spouští vaši funkci podle plánu pomocí výrazu CRON.
|
||||
* **databaseEvent**: Spouští se při událostech životního cyklu objektů v pracovním prostoru. Když je operace události `updated`, lze konkrétní sledovaná pole určit v poli `updatedFields`. Pokud zůstane nedefinované nebo prázdné, spustí funkci jakákoli aktualizace.
|
||||
> např. `person.updated`, `*.created`, `company.*`
|
||||
|
||||
<Note>
|
||||
Funkci můžete také spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
|
||||
```
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
Logy můžete sledovat pomocí:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty logs
|
||||
```
|
||||
</Note>
|
||||
|
||||
#### Payload spouštěče trasy
|
||||
|
||||
Když spouštěč typu route vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá
|
||||
[AWS HTTP API v2 formátu](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html).
|
||||
Importujte typ `RoutePayload` z `twenty-sdk`:
|
||||
|
||||
```ts
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const { headers, queryStringParameters, pathParameters, body } = event;
|
||||
const { method, path } = event.requestContext.http;
|
||||
|
||||
return { message: 'Success' };
|
||||
};
|
||||
```
|
||||
|
||||
Typ `RoutePayload` má následující strukturu:
|
||||
|
||||
| Vlastnost | Typ | Popis | Příklad |
|
||||
| ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `headers` | `Record\<string, string \| undefined>` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | viz sekci níže |
|
||||
| `queryStringParameters` | `Record\<string, string \| undefined>` | Parametry query stringu (více hodnot spojených čárkami) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
|
||||
| `pathParameters` | `Record\<string, string \| undefined>` | Parametry cesty extrahované ze vzoru trasy | `/users/:id`, `/users/123` -> `{ id: '123' }` |
|
||||
| `body` | `object \| null` | Parsované tělo požadavku (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
|
||||
| `isBase64Encoded` | `boolean` | Zda je tělo kódováno base64 | |
|
||||
| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | |
|
||||
| `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | |
|
||||
|
||||
|
||||
#### forwardedRequestHeaders
|
||||
|
||||
Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají.
|
||||
Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'webhook-handler',
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/webhook',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: false,
|
||||
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Ve vašem handleru k přeposlaným záhlavím přistupujte takto:
|
||||
|
||||
```ts
|
||||
const handler = async (event: RoutePayload) => {
|
||||
const signature = event.headers['x-webhook-signature'];
|
||||
const contentType = event.headers['content-type'];
|
||||
|
||||
// Validate webhook signature...
|
||||
return { received: true };
|
||||
};
|
||||
```
|
||||
|
||||
<Note>
|
||||
Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`).
|
||||
</Note>
|
||||
|
||||
#### Zpřístupnění funkce jako nástroje
|
||||
|
||||
Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji použít v automatizacích pracovních postupů.
|
||||
|
||||
Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true`:
|
||||
|
||||
```ts src/logic-functions/enrich-company.logic-function.ts
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
const handler = async (params: { companyName: string; domain?: string }) => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
const result = await client.mutation({
|
||||
createTask: {
|
||||
__args: {
|
||||
data: {
|
||||
title: `Enrich data for ${params.companyName}`,
|
||||
body: `Domain: ${params.domain ?? 'unknown'}`,
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { taskId: result.createTask.id };
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
||||
name: 'enrich-company',
|
||||
description: 'Enrich a company record with external data',
|
||||
timeoutSeconds: 10,
|
||||
handler,
|
||||
isTool: true,
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) a současně se spouštět událostmi.
|
||||
* **`toolInputSchema`** (volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Schéma se určuje automaticky ze statické analýzy zdrojového kódu, ale můžete ho nastavit i explicitně:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
...,
|
||||
toolInputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
companyName: {
|
||||
type: 'string',
|
||||
description: 'The name of the company to enrich',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
description: 'The company website domain (optional)',
|
||||
},
|
||||
},
|
||||
required: ['companyName'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Napište kvalitní `description`.** Agenti AI se spoléhají na pole funkce `description` při rozhodování, kdy nástroj použít. Buďte konkrétní ohledně toho, co nástroj dělá a kdy se má volat.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePostInstallLogicFunction" description="Definujte postinstalační logickou funkci (jedna na aplikaci)">
|
||||
|
||||
Postinstalační funkce je logická funkce, která se spustí automaticky, jakmile je instalace vaší aplikace v pracovním prostoru dokončena. Server ji provede **poté**, co byla synchronizována metadata aplikace a vygenerován klient SDK, takže je pracovní prostor plně připraven k použití a nové schéma je zavedeno. Mezi typické případy použití patří naplnění výchozími daty, vytvoření počátečních záznamů, konfigurace nastavení pracovního prostoru nebo zřizování prostředků ve službách třetích stran.
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (payload: InstallPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: false,
|
||||
shouldRunSynchronously: false,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Obslužná funkce obdrží `InstallPayload` s `{ previousVersion?: string; newVersion: string }` — `newVersion` je verze, která se instaluje, a `previousVersion` je verze, která byla nainstalována dříve (nebo `undefined` při čisté instalaci). Tyto hodnoty použijte k rozlišení čistých instalací od aktualizací a ke spuštění migrační logiky specifické pro verzi.
|
||||
* **Kdy se hook spouští**: ve výchozím nastavení pouze při čistých instalacích. Předejte `shouldRunOnVersionUpgrade: true`, pokud chcete, aby se spouštěl i při aktualizaci aplikace z předchozí verze. Pokud je vynechán, příznak má výchozí hodnotu `false` a při aktualizacích se hook přeskočí.
|
||||
* **Model provádění — ve výchozím nastavení asynchronní, synchronní volitelně**: příznak `shouldRunSynchronously` určuje *jak* se spouští post-install.
|
||||
* `shouldRunSynchronously: false` *(výchozí)* — hook je **zařazen do fronty zpráv** s `retryLimit: 3` a běží asynchronně ve workeru. Odezva instalace se vrátí hned po zařazení úlohy do fronty, takže pomalá nebo chybující obslužná funkce neblokuje volajícího. Worker se pokusí o opakování až třikrát. **Použijte pro dlouho běžící úlohy** — plnění velkých datových sad, volání pomalých externích API, zřizování externích prostředků, cokoli, co by mohlo přesáhnout rozumné časové okno HTTP odezvy.
|
||||
* `shouldRunSynchronously: true` — hook se provádí **inline během instalačního procesu** (stejný vykonavatel jako pre-install). Instalační požadavek blokuje, dokud obslužná funkce nedokončí, a pokud vyvolá výjimku, volající instalace obdrží `POST_INSTALL_ERROR`. Žádné automatické opakování. **Použijte pro rychlé úlohy, které se musí dokončit před odpovědí** — například vrácení validační chyby uživateli nebo rychlé nastavení, na kterém bude klient záviset ihned po návratu volání instalace. Mějte na paměti, že v době, kdy se spustí post-install, už byla migrace metadat aplikována, takže selhání v synchronním režimu změny schématu **ne**vrací zpět — pouze odhalí chybu.
|
||||
* Ujistěte se, že vaše obslužná funkce je idempotentní. V asynchronním režimu se může fronta pokusit až třikrát; v obou režimech se může hook znovu spustit při aktualizacích, pokud je `shouldRunOnVersionUpgrade: true`.
|
||||
* Proměnné prostředí `APPLICATION_ID`, `APP_ACCESS_TOKEN` a `API_URL` jsou dostupné uvnitř obslužné funkce (stejně jako u jakékoli jiné logické funkce), takže můžete volat Twenty API s aplikačním přístupovým tokenem omezeným na vaši aplikaci.
|
||||
* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna.
|
||||
* Atributy funkce `universalIdentifier`, `shouldRunOnVersionUpgrade` a `shouldRunSynchronously` jsou během buildu automaticky připojeny k manifestu aplikace do pole `postInstallLogicFunction` — není potřeba je uvádět v `defineApplication()`.
|
||||
* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty.
|
||||
* **Nespouští se v režimu dev**: když je aplikace registrována lokálně (pomocí `yarn twenty dev`), server zcela přeskočí instalační tok a synchronizuje soubory přímo prostřednictvím sledovače CLI — takže se post-install v režimu dev nikdy nespustí bez ohledu na `shouldRunSynchronously`. Použijte `yarn twenty exec --postInstall` k ručnímu spuštění nad běžícím pracovním prostorem.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePreInstallLogicFunction" description="Definujte předinstalační logickou funkci (jedna na aplikaci)">
|
||||
|
||||
Funkce pre-install je logická funkce, která se během instalace spouští automaticky, **před aplikováním migrace metadat pracovního prostoru**. Má stejný tvar payloadu jako post-install (`InstallPayload`), ale je zařazena dříve v instalačním toku, aby mohla připravit stav, na němž nadcházející migrace závisí — typické použití zahrnuje zálohování dat, ověření kompatibility s novým schématem nebo archivaci záznamů, které se chystají přeuspořádat nebo odstranit.
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (payload: InstallPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: true,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --preInstall
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* Funkce pre-install používají `definePreInstallLogicFunction()` — stejné specializované nastavení jako u post-install, pouze připojené k jiné fázi životního cyklu.
|
||||
* Obě obslužné funkce pre- i post-install přijímají stejný typ `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importujte jej jednou a znovu použijte pro oba hooky.
|
||||
* **Kdy se hook spouští**: umístěn těsně před migrací metadat pracovního prostoru (`synchronizeFromManifest`). Před spuštěním server provede čistě aditivní "zjednodušenou synchronizaci", která v metadatech pracovního prostoru zaregistruje pre-install funkci **nové** verze — ničeho dalšího se nedotkne — a poté ji spustí. Protože tato synchronizace je pouze aditivní, objekty, pole a data předchozí verze zůstávají při spuštění vaší obslužné funkce zachována: můžete bezpečně číst a zálohovat stav před migrací.
|
||||
* **Model provádění**: pre-install se provádí **synchronně** a **blokuje instalaci**. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před aplikováním jakýchkoli změn schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci.
|
||||
* Stejně jako u post-install je na jednu aplikaci povolena pouze jedna funkce pre-install. Během buildu je automaticky připojena k manifestu aplikace pod `preInstallLogicFunction`.
|
||||
* **Nespouští se v režimu dev**: stejně jako u post-install — u lokálně registrovaných aplikací je instalační tok zcela přeskočen, takže se pre-install pod `yarn twenty dev` nikdy nespustí. Použijte `yarn twenty exec --preInstall` k ručnímu spuštění.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Pre-install vs post-install: kdy použít který" description="Výběr správného instalačního hooku">
|
||||
|
||||
Oba hooky jsou součástí téhož instalačního toku a přijímají stejný `InstallPayload`. Rozdíl je v tom, **kdy** se spouštějí vzhledem k migraci metadat pracovního prostoru, a to určuje, jakých dat se mohou bezpečně dotýkat.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ install flow │
|
||||
│ │
|
||||
│ upload package → [pre-install] → metadata migration → │
|
||||
│ generate SDK → [post-install] │
|
||||
│ │
|
||||
│ old schema visible new schema visible │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Pre-install je vždy **synchronní** (blokuje instalaci a může ji přerušit). Post-install je **ve výchozím nastavení asynchronní** — zařazen do workeru s automatickými pokusy o opakování — ale může přejít na synchronní provádění pomocí `shouldRunSynchronously: true`. Viz accordion `definePostInstallLogicFunction` výše, kdy použít jednotlivé režimy.
|
||||
|
||||
**Použijte `post-install` pro cokoli, co vyžaduje existenci nového schématu.** To je běžný případ:
|
||||
|
||||
* Plnění výchozími daty (vytváření počátečních záznamů, výchozích pohledů, demo obsahu) vůči nově přidaným objektům a polím.
|
||||
* Registrace webhooků u služeb třetích stran poté, co má aplikace své přihlašovací údaje.
|
||||
* Volání vlastního API k dokončení nastavení, které závisí na synchronizovaných metadatech.
|
||||
* Idempotentní logika "zajisti, že to existuje", která má při každé aktualizaci uvést stav do souladu — kombinujte s `shouldRunOnVersionUpgrade: true`.
|
||||
|
||||
Příklad — po instalaci naplňte výchozí záznam `PostCard`:
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
import { createClient } from './generated/client';
|
||||
|
||||
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
|
||||
if (previousVersion) return; // fresh installs only
|
||||
|
||||
const client = createClient();
|
||||
await client.postCard.create({
|
||||
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
|
||||
});
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||||
name: 'post-install',
|
||||
description: 'Seeds a welcome post card after install.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: false,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**Použijte `pre-install`, pokud by migrace jinak zničila nebo poškodila existující data.** Protože pre-install běží proti *předchozímu* schématu a jeho selhání vrací aktualizaci zpět, je to správné místo pro cokoli rizikového:
|
||||
|
||||
* **Zálohování dat, která se chystají odstranit nebo přeuspořádat** — např. odstraňujete pole ve verzi v2 a potřebujete jeho hodnoty zkopírovat do jiného pole nebo je před spuštěním migrace exportovat do úložiště.
|
||||
* **Archivace záznamů, které by nové omezení zneplatnilo** — např. pole se stává `NOT NULL` a je třeba nejprve smazat nebo opravit řádky s hodnotami null.
|
||||
* **Ověření kompatibility a odmítnutí aktualizace, pokud nelze aktuální data čistě migrovat** — vyhoďte výjimku z obslužné funkce a instalace se ukončí bez provedených změn. Je to bezpečnější, než zjistit nekompatibilitu uprostřed migrace.
|
||||
* **Přejmenování nebo změna klíčů dat** před změnou schématu, která by ztratila vazby.
|
||||
|
||||
Příklad — archivujte záznamy před destruktivní migrací:
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
import { createClient } from './generated/client';
|
||||
|
||||
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
|
||||
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
|
||||
if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = createClient();
|
||||
const legacyRecords = await client.postCard.findMany({
|
||||
where: { notes: { isNotNull: true } },
|
||||
});
|
||||
|
||||
if (legacyRecords.length === 0) return;
|
||||
|
||||
// Copy legacy `notes` into the new `description` field before the migration
|
||||
// drops the `notes` column. If this fails, the upgrade is aborted and the
|
||||
// workspace stays on v1 with all data intact.
|
||||
await Promise.all(
|
||||
legacyRecords.map((record) =>
|
||||
client.postCard.update({
|
||||
where: { id: record.id },
|
||||
data: { description: record.notes },
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
|
||||
name: 'pre-install',
|
||||
description: 'Backs up legacy notes into description before the v2 migration.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: true,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**Zlaté pravidlo:**
|
||||
|
||||
| You want to... | Použít |
|
||||
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` |
|
||||
| Spusťte dlouho běžící plnění nebo volání třetích stran, která by neměla blokovat odezvu instalace | `post-install` (výchozí — `shouldRunSynchronously: false`, s opakovanými pokusy workeru) |
|
||||
| Spusťte rychlé nastavení, na které bude volající spoléhat ihned po návratu volání instalace | `post-install` s `shouldRunSynchronously: true` |
|
||||
| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` |
|
||||
| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) |
|
||||
| Spouštějte srovnání stavu při každé aktualizaci | `post-install` s `shouldRunOnVersionUpgrade: true` |
|
||||
| Proveďte jednorázové nastavení pouze při první instalaci | `post-install` s `shouldRunOnVersionUpgrade: false` (výchozí) |
|
||||
|
||||
<Note>
|
||||
Pokud si nejste jisti, výchozí volbou je **post-install**. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Typovaní klienti API (twenty-client-sdk)
|
||||
|
||||
Balíček `twenty-client-sdk` poskytuje dva typované klienty GraphQL pro práci s Twenty API z vašich logických funkcí a frontendových komponent.
|
||||
|
||||
| Klient | Importovat | Koncový bod | Generováno? |
|
||||
| ------------------- | ---------------------------- | ---------------------------------------------------------------- | ------------------------------ |
|
||||
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — data pracovního prostoru (záznamy, objekty) | Ano, při vývoji/sestavení |
|
||||
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — konfigurace pracovního prostoru, nahrávání souborů | Ne, dodává se předem sestavený |
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="CoreApiClient" description="Dotazování a změny dat pracovního prostoru (záznamy, objekty)">
|
||||
|
||||
`CoreApiClient` je hlavní klient pro dotazování a mutace dat pracovního prostoru. Generuje se z vašeho schématu pracovního prostoru během `yarn twenty dev` nebo `yarn twenty build`, takže je plně typovaný tak, aby odpovídal vašim objektům a polím.
|
||||
|
||||
```ts
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
|
||||
const client = new CoreApiClient();
|
||||
|
||||
// Query records
|
||||
const { companies } = await client.query({
|
||||
companies: {
|
||||
edges: {
|
||||
node: {
|
||||
id: true,
|
||||
name: true,
|
||||
domainName: {
|
||||
primaryLinkLabel: true,
|
||||
primaryLinkUrl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create a record
|
||||
const { createCompany } = await client.mutation({
|
||||
createCompany: {
|
||||
__args: {
|
||||
data: {
|
||||
name: 'Acme Corp',
|
||||
},
|
||||
},
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Klient používá syntaxi výběrové sady (selection-set): předáním `true` zahrnete pole, pro argumenty použijte `__args` a pro relace vnořujte objekty. Získáte plné automatické doplňování a kontrolu typů založené na schématu vašeho pracovního prostoru.
|
||||
|
||||
<Note>
|
||||
**CoreApiClient je generován při vývoji/sestavení.** Pokud jej použijete bez předchozího spuštění `yarn twenty dev` nebo `yarn twenty build`, vyvolá chybu. Generování probíhá automaticky — CLI prozkoumá GraphQL schéma vašeho pracovního prostoru a vygeneruje typovaného klienta pomocí `@genql/cli`.
|
||||
</Note>
|
||||
|
||||
#### Použití CoreSchema pro anotace typů
|
||||
|
||||
`CoreSchema` poskytuje typy TypeScriptu odpovídající objektům vašeho pracovního prostoru — hodí se pro typování stavu komponent nebo parametrů funkcí:
|
||||
|
||||
```ts
|
||||
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
|
||||
import { useState } from 'react';
|
||||
|
||||
const [company, setCompany] = useState<
|
||||
Pick<CoreSchema.Company, 'id' | 'name'> | undefined
|
||||
>(undefined);
|
||||
|
||||
const client = new CoreApiClient();
|
||||
const result = await client.query({
|
||||
company: {
|
||||
__args: { filter: { position: { eq: 1 } } },
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
setCompany(result.company);
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="MetadataApiClient" description="Konfigurace pracovního prostoru, aplikace a nahrávání souborů">
|
||||
|
||||
`MetadataApiClient` je dodáván předem sestavený v rámci SDK (není vyžadováno žádné generování). Odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru, aplikace a nahrávání souborů.
|
||||
|
||||
```ts
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
// List first 10 objects in the workspace
|
||||
const { objects } = await metadataClient.query({
|
||||
objects: {
|
||||
edges: {
|
||||
node: {
|
||||
id: true,
|
||||
nameSingular: true,
|
||||
namePlural: true,
|
||||
labelSingular: true,
|
||||
isCustom: true,
|
||||
},
|
||||
},
|
||||
__args: {
|
||||
filter: {},
|
||||
paging: { first: 10 },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Nahrávání souborů
|
||||
|
||||
`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru:
|
||||
|
||||
```ts
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const fileBuffer = fs.readFileSync('./invoice.pdf');
|
||||
|
||||
const uploadedFile = await metadataClient.uploadFile(
|
||||
fileBuffer, // file contents as a Buffer
|
||||
'invoice.pdf', // filename
|
||||
'application/pdf', // MIME type
|
||||
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier
|
||||
);
|
||||
|
||||
console.log(uploadedFile);
|
||||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||||
```
|
||||
|
||||
| Parametr | Typ | Popis |
|
||||
| ---------------------------------- | -------- | ------------------------------------------------------------------- |
|
||||
| `fileBuffer` | `Buffer` | Surový obsah souboru |
|
||||
| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) |
|
||||
| `contentType` | `string` | Typ MIME (pokud je vynechán, výchozí je `application/octet-stream`) |
|
||||
| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu |
|
||||
|
||||
Hlavní body:
|
||||
* Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje v jakémkoli pracovním prostoru, kde je vaše aplikace nainstalována.
|
||||
* Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
Když váš kód běží na Twenty (logické funkce nebo frontendové komponenty), platforma vloží přihlašovací údaje jako proměnné prostředí:
|
||||
|
||||
* `TWENTY_API_URL` — Základní URL Twenty API
|
||||
* `TWENTY_APP_ACCESS_TOKEN` — krátkodobý klíč s rozsahem omezeným na výchozí roli funkce vaší aplikace
|
||||
|
||||
Není nutné je předávat klientům — čtou je automaticky z `process.env`. Oprávnění API klíče jsou určena rolí uvedenou v `defaultRoleUniversalIdentifier` ve vašem `application-config.ts`.
|
||||
</Note>
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
---
|
||||
title: Publikování
|
||||
icon: nahrát
|
||||
description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte interně.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
## Přehled
|
||||
|
||||
Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: Dovednosti a agenti
|
||||
description: Define AI skills and agents for your app.
|
||||
icon: robot
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Skills and agents are currently in alpha. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineSkill" description="Definujte dovednosti agenta AI">
|
||||
|
||||
Dovednosti definují znovupoužitelné pokyny a schopnosti, které mohou agenti AI používat ve vašem pracovním prostoru. K definování dovedností s vestavěnou validací použijte `defineSkill()`:
|
||||
|
||||
```ts src/skills/example-skill.ts
|
||||
import { defineSkill } from 'twenty-sdk/define';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'sales-outreach',
|
||||
label: 'Sales Outreach',
|
||||
description: 'Guides the AI agent through a structured sales outreach process',
|
||||
icon: 'IconBrain',
|
||||
content: `You are a sales outreach assistant. When reaching out to a prospect:
|
||||
1. Research the company and recent news
|
||||
2. Identify the prospect's role and likely pain points
|
||||
3. Draft a personalized message referencing specific details
|
||||
4. Keep the tone professional but conversational`,
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* `name` je jedinečný identifikátor dovednosti (doporučuje se kebab-case).
|
||||
* `label` je uživatelsky čitelný název zobrazovaný v UI.
|
||||
* `content` obsahuje pokyny dovednosti — je to text, který agent AI používá.
|
||||
* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI.
|
||||
* `description` (volitelné) poskytuje doplňující kontext o účelu dovednosti.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineAgent" description="Definujte AI agenty s vlastními prompty">
|
||||
|
||||
Agenti jsou asistenti AI, kteří běží ve vašem pracovním prostoru. K vytvoření agentů s vlastním systémovým promptem použijte `defineAgent()`:
|
||||
|
||||
```ts src/agents/example-agent.ts
|
||||
import { defineAgent } from 'twenty-sdk/define';
|
||||
|
||||
export default defineAgent({
|
||||
universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
|
||||
name: 'sales-assistant',
|
||||
label: 'Sales Assistant',
|
||||
description: 'Helps the sales team draft outreach emails and research prospects',
|
||||
icon: 'IconRobot',
|
||||
prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.',
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* `name` je jedinečný identifikátor agenta (doporučuje se kebab-case).
|
||||
* `label` je zobrazovaný název v UI.
|
||||
* `prompt` je systémový prompt, který definuje chování agenta.
|
||||
* `description` (volitelné) poskytuje kontext o tom, co agent dělá.
|
||||
* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI.
|
||||
* `modelId` (volitelné) přepíše výchozí model AI používaný agentem.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
189
packages/twenty-docs/l/cs/developers/extend/oauth.mdx
Normal file
189
packages/twenty-docs/l/cs/developers/extend/oauth.mdx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
title: OAuth
|
||||
icon: klíč
|
||||
description: Authorization code flow with PKCE and client credentials for server-to-server access.
|
||||
---
|
||||
|
||||
Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard.
|
||||
|
||||
## When to Use OAuth
|
||||
|
||||
| Scénář | Auth Method |
|
||||
| --------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| Internal scripts, automation | [API Key](/l/cs/developers/extend/api#authentication) |
|
||||
| External app acting on behalf of a user | **OAuth — Authorization Code** |
|
||||
| Server-to-server, no user context | **OAuth — Client Credentials** |
|
||||
| Twenty App with UI extensions | [Apps](/l/cs/developers/extend/apps/getting-started) (OAuth is handled automatically) |
|
||||
|
||||
## Register a Client
|
||||
|
||||
Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically:
|
||||
|
||||
```bash
|
||||
POST /oauth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"client_name": "My Integration",
|
||||
"redirect_uris": ["https://myapp.com/callback"],
|
||||
"grant_types": ["authorization_code"],
|
||||
"token_endpoint_auth_method": "client_secret_post"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": "abc123",
|
||||
"client_secret": "secret456",
|
||||
"client_name": "My Integration",
|
||||
"redirect_uris": ["https://myapp.com/callback"]
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Store the `client_secret` securely — it cannot be retrieved later.
|
||||
</Warning>
|
||||
|
||||
## Oprávnění
|
||||
|
||||
| Scope | Přístup |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| `api` | Full read/write access to the Core and Metadata APIs |
|
||||
| `profil` | Read the authenticated user's profile information |
|
||||
|
||||
Request scopes as a space-separated string: `scope=api profile`
|
||||
|
||||
## Authorization Code Flow
|
||||
|
||||
Use this flow when your app acts on behalf of a Twenty user.
|
||||
|
||||
### 1. Redirect the user to authorize
|
||||
|
||||
```
|
||||
GET /oauth/authorize?
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
response_type=code&
|
||||
redirect_uri=https://myapp.com/callback&
|
||||
scope=api&
|
||||
state=random_state_value&
|
||||
code_challenge=CHALLENGE&
|
||||
code_challenge_method=S256
|
||||
```
|
||||
|
||||
| Parametr | Povinné | Popis |
|
||||
| ----------------------- | ---------- | ------------------------------------------------------------ |
|
||||
| `client_id` | Ano | Your registered client ID |
|
||||
| `response_type` | Ano | Must be `code` |
|
||||
| `redirect_uri` | Ano | Must match a registered redirect URI |
|
||||
| `scope` | Ne | Space-separated scopes (defaults to `api`) |
|
||||
| `stav` | Doporučeno | Random string to prevent CSRF attacks |
|
||||
| `code_challenge` | Doporučeno | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) |
|
||||
| `code_challenge_method` | Doporučeno | Must be `S256` when using PKCE |
|
||||
|
||||
The user sees a consent screen and approves or denies access.
|
||||
|
||||
### 2. Handle the callback
|
||||
|
||||
After authorization, Twenty redirects back to your `redirect_uri`:
|
||||
|
||||
```
|
||||
https://myapp.com/callback?code=AUTH_CODE&state=random_state_value
|
||||
```
|
||||
|
||||
Verify that `state` matches what you sent.
|
||||
|
||||
### 3. Exchange the code for tokens
|
||||
|
||||
```bash
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&
|
||||
code=AUTH_CODE&
|
||||
redirect_uri=https://myapp.com/callback&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET&
|
||||
code_verifier=YOUR_PKCE_VERIFIER
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbG...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "dGhpcyBpcyBh..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Use the access token
|
||||
|
||||
```bash
|
||||
GET /rest/companies
|
||||
Authorization: Bearer ACCESS_TOKEN
|
||||
```
|
||||
|
||||
### 5. Refresh when expired
|
||||
|
||||
```bash
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=refresh_token&
|
||||
refresh_token=YOUR_REFRESH_TOKEN&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET
|
||||
```
|
||||
|
||||
## Client Credentials Flow
|
||||
|
||||
For server-to-server integrations with no user interaction:
|
||||
|
||||
```bash
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET&
|
||||
scope=api
|
||||
```
|
||||
|
||||
The returned token has workspace-level access, not tied to any specific user.
|
||||
|
||||
## Server Discovery
|
||||
|
||||
Twenty publishes its OAuth configuration at a standard discovery endpoint:
|
||||
|
||||
```
|
||||
GET /.well-known/oauth-authorization-server
|
||||
```
|
||||
|
||||
This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients.
|
||||
|
||||
## API Endpoints Summary
|
||||
|
||||
| Koncový bod | Účel |
|
||||
| ----------------------------------------- | --------------------------- |
|
||||
| `/.well-known/oauth-authorization-server` | Server metadata discovery |
|
||||
| `/oauth/register` | Dynamic client registration |
|
||||
| `/oauth/authorize` | User authorization |
|
||||
| `/oauth/token` | Token exchange and refresh |
|
||||
|
||||
| Prostředí | Základní URL |
|
||||
| ------------------- | ------------------------ |
|
||||
| **Cloud** | `https://api.twenty.com` |
|
||||
| **Vlastní hosting** | `https://{your-domain}` |
|
||||
|
||||
## OAuth vs API Keys
|
||||
|
||||
| | API Klíče | OAuth |
|
||||
| ------------------ | ----------------------- | -------------------------------------- |
|
||||
| **Nastavení** | Generate in Settings | Register a client, implement flow |
|
||||
| **User context** | None (workspace-level) | Specific user's permissions |
|
||||
| **Vhodné pro** | Scripts, internal tools | External apps, multi-user integrations |
|
||||
| **Token rotation** | Ruční | Automatic via refresh tokens |
|
||||
| **Scoped access** | Full API access | Granular via scopes |
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
---
|
||||
title: Webhooky
|
||||
description: Dostávejte oznámení v reálném čase, když ve vašem CRM dojde k událostem.
|
||||
icon: satellite-dish
|
||||
description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Webhooky posílají data do vašich systémů v reálném čase, když v Twenty dojde k událostem — bez potřeby průběžného dotazování. Použijte je k udržování externích systémů v synchronizaci, spouštění automatizací nebo zasílání upozornění.
|
||||
Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects.
|
||||
|
||||
## Vytvořit Webhook
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,28 @@
|
|||
---
|
||||
title: Začínáme
|
||||
description: Vítejte v dokumentaci pro vývojáře Twenty, která je vaším zdrojem informací pro rozšiřování, vlastní hostování a přispívání do Twenty.
|
||||
title: Vývojáři
|
||||
description: Build apps, use the API, self-host, or contribute to the codebase.
|
||||
---
|
||||
|
||||
import { CardTitle } from "/snippets/card-title.mdx"
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card href="/l/cs/developers/extend/extend" img="/images/user-guide/integrations/plug.png">
|
||||
<CardTitle>Rozšiřte</CardTitle>
|
||||
Vytvářejte integrace pomocí API, webhooků a vlastních aplikací.
|
||||
<Card href="/l/cs/developers/extend/apps/getting-started" img="/images/user-guide/halftone/dev-apps.png">
|
||||
<CardTitle>Apps</CardTitle>
|
||||
Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/cs/developers/self-host/self-host" img="/images/user-guide/what-is-twenty/20.png">
|
||||
<CardTitle>Hostujte sami</CardTitle>
|
||||
Nasaďte a spravujte Twenty na vlastní infrastruktuře.
|
||||
<Card href="/l/cs/developers/extend/api" img="/images/user-guide/halftone/dev-api.png">
|
||||
<CardTitle>API</CardTitle>
|
||||
REST and GraphQL APIs, webhooks, and OAuth.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/cs/developers/contribute/contribute" img="/images/user-guide/github/github-header.png">
|
||||
<CardTitle>Přispějte</CardTitle>
|
||||
Připojte se k naší open-source komunitě a přispívejte do Twenty.
|
||||
<Card href="/l/cs/developers/self-host/capabilities/docker-compose" img="/images/user-guide/halftone/dev-self-host.png">
|
||||
<CardTitle>Self-Host</CardTitle>
|
||||
Run Twenty on your own infrastructure.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/cs/developers/contribute/capabilities/local-setup" img="/images/user-guide/halftone/dev-contribute.png">
|
||||
<CardTitle>Contribute</CardTitle>
|
||||
Set up the monorepo locally and submit PRs.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Další metody
|
||||
icon: cloud
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Docker Compose jedním kliknutím
|
||||
title: Docker Compose
|
||||
icon: docker
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Nastavení
|
||||
icon: gear
|
||||
---
|
||||
|
||||
# Správa konfigurace
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Řešení potíží
|
||||
icon: wrench
|
||||
---
|
||||
|
||||
## Řešení potíží
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Průvodce upgradem
|
||||
icon: arrow-up-right-dots
|
||||
---
|
||||
|
||||
## Obecné pokyny
|
||||
|
|
@ -16,366 +17,14 @@ Pokud jste použili Docker Compose, postupujte takto:
|
|||
|
||||
3. Opětovně zapněte Twenty pomocí `docker compose up -d`
|
||||
|
||||
Chcete-li upgradovat svou instanci o několik verzí, například z v0.33.0 na v0.35.0, musíte svou instanci postupně upgradovat, v tomto příkladu z v0.33.0 na v0.34.0 a poté z v0.34.0 na v0.35.0.
|
||||
|
||||
**Ujistěte se, že máte po každém upgradu nepoškozenou zálohu.**
|
||||
|
||||
## Kroky upgradu specifické pro danou verzi
|
||||
|
||||
## v1.0
|
||||
## After v1.21
|
||||
|
||||
Ahoj Twenty v1.0! 🎉
|
||||
We know support sequential upgrades. You don't need to go through each version one by one.
|
||||
|
||||
## v0.60
|
||||
## Before v1.21
|
||||
|
||||
### Vylepšení výkonu
|
||||
|
||||
Všechny interakce s metadatovým API byly optimalizovány pro lepší výkon, zejména pro manipulaci s metadaty objektů a operace vytváření pracovních prostorů.
|
||||
|
||||
Refaktorovali jsme naši strategii mezipaměti tak, aby upřednostňovala, pokud je to možné, zásahy do mezipaměti před databázovými dotazy, což výrazně zlepšilo výkon operací rozhraní API metadat.
|
||||
|
||||
Pokud po upgradu narazíte na problémy s výkonem, může být nutné vyprázdnit cache, aby bylo zajištěno její sladění s nejnovějšími změnami. Spusťte tento příkaz v kontejneru twenty-server:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
### v0.55
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.55 image
|
||||
|
||||
Už nemusíte spouštět žádný příkaz, nový obraz se automaticky postará o spuštění všech požadovaných migrací.
|
||||
|
||||
### Chyba `Uživatel nemá oprávnění`
|
||||
|
||||
Pokud po upgradu narazíte na chyby autorizace na většině požadavků, může být nutné vyprázdnit cache, abyste znovu provedli vyhodnocení nejnovějších oprávnění.
|
||||
|
||||
Ve svém kontejneru `twenty-server` spusťte:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
Tento problém je specifický pro tuto verzi Twenty a neměl by být vyžadován pro budoucí upgrady.
|
||||
|
||||
### v0.54
|
||||
|
||||
Od verze `0.53`, nejsou nutné žádné manuální akce.
|
||||
|
||||
#### Omezení metadatového schématu
|
||||
|
||||
Sloučili jsme schéma `metadata` do `core`, abychom zjednodušili načítání dat z `TypeORM`.
|
||||
Sloučili jsme krok příkazu `migrate` do příkazu `upgrade`. Nedoporučujeme ručně spouštět `migrate` v žádném z vašich kontejnerů server/worker.
|
||||
|
||||
### Od v0.53
|
||||
|
||||
Od `0.53` je upgrade programově upraven v rámci `DockerFile`, což znamená, že od této chvíle již nemusíte ručně spouštět žádný příkaz.
|
||||
|
||||
Ujistěte se, že upgradujete svou instanci postupně, aniž byste přeskočili hlavní verzi (např. `0.43.3` na `0.44.0` je povoleno, ale `0.43.1` na `0.45.0` nikoli), aby se předešlo asynchronizaci verzí pracovního prostoru, což by mohlo mít za následek chybu při běhu a chybějící funkčnost.
|
||||
|
||||
Chcete-li zkontrolovat, zda byl pracovní prostor správně migrován, můžete zkontrolovat jeho verzi v databázi v tabulce `core.workspace`.
|
||||
|
||||
Měla by se vždy nacházet v rozmezí vaší aktuální instance Twenty `major.minor` verze. Můžete zkontrolovat verzi instance na ovládacím panelu administrátora (na `/settings/admin-panel`, přístupné, pokud máte v databázi nastaveno uživatelské vlastnosti `canAccessFullAdminPanel` na true) nebo spuštěním `echo $APP_VERSION` ve vašem kontejneru `twenty-server`.
|
||||
|
||||
Chcete-li opravit asynchronizaci verzí pracovního prostoru, budete muset upgradovat z odpovídající verze twenty podle souvisejícího průvodce upgradem po jednotlivých krocích, až do dosažení požadované verze.
|
||||
|
||||
#### Odstranění `auditLog`
|
||||
|
||||
Odstranili jsme standardní objekt auditLog, což znamená, že váš záložní soubor může být po této migraci výrazně zmenšen.
|
||||
|
||||
### v0.51 až v0.52
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.52 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### Mám pracovní prostor zablokovaný ve verzi mezi `0.52.0` a `0.52.6`
|
||||
|
||||
Bohužel `0.52.0` a `0.52.6` byly zcela odstraněny z dockerHub.
|
||||
Budete muset ručně změnit verzi pracovního prostoru na `0.51.0` v databázi a upgradovat pomocí twenty verze `0.52.11` podle jejího výše uvedeného průvodce upgradem.
|
||||
|
||||
### v0.50 až v0.51
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.51 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### v0.44.0 až v0.50.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.50.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### Mutace Docker-compose.yml
|
||||
|
||||
Tato verze obsahuje mutaci `docker-compose.yml`, která zajišťuje, že služba `worker` má přístup k objemu `server-local-data`.
|
||||
Aktualizujte svůj místní `docker-compose.yml` pomocí [docker-compose.yml v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
|
||||
|
||||
### v0.43.0 až v0.44.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.44.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### v0.42.0 až v0.43.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.43.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
V této verzi jsme také přešli na obraz postgres:16 v docker-compose.yml.
|
||||
|
||||
#### (Možnost 1) Migrace databáze
|
||||
|
||||
Zachování existujícího obrazu postgres-spilo je v pořádku, ale budete muset zmrazit verzi v `docker-compose.yml` na 0.43.0.
|
||||
|
||||
#### (Možnost 2) Migrace databáze
|
||||
|
||||
Pokud chcete migrovat svou databázi na nový obraz postgres:16, postupujte podle těchto kroků:
|
||||
|
||||
1. Zálohujte svou databázi z kontejneru postgres-spilo
|
||||
|
||||
```
|
||||
docker exec -it twenty-db-1 sh
|
||||
pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql
|
||||
exit
|
||||
docker cp twenty-db-1:/home/postgres/databases_backup.sql .
|
||||
```
|
||||
|
||||
Ujistěte se, že váš záložní soubor není prázdný.
|
||||
|
||||
2. Upgradujte svůj `docker-compose.yml` na použití obrazu postgres:16 podle [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) soubor.
|
||||
|
||||
3. Obnovte databázi do nového kontejneru postgres:16
|
||||
|
||||
```
|
||||
docker cp databases_backup.sql twenty-db-1:/databases_backup.sql
|
||||
docker exec -it twenty-db-1 sh
|
||||
psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql
|
||||
exit
|
||||
```
|
||||
|
||||
### v0.41.0 až v0.42.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.42.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.42
|
||||
```
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Odstraněno: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
|
||||
* Přidáno: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
|
||||
|
||||
### v0.40.0 až v0.41.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.41.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.41
|
||||
```
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Odstraněno: `AUTH_MICROSOFT_TENANT_ID`
|
||||
|
||||
### v0.35.0 až v0.40.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.40.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.40
|
||||
```
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Přidáno: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
|
||||
|
||||
### v0.34.0 až v0.35.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.35.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.35
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.35` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Nahradili jsme `ENABLE_DB_MIGRATIONS` s `DISABLE_DB_MIGRATIONS` (výchozí hodnota je nyní `false`, pravděpodobně nemusíte nastavovat nic)
|
||||
|
||||
### v0.33.0 až v0.34.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.34.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.34
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.34` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Odstraněno: `FRONT_BASE_URL`
|
||||
* Přidáno: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
|
||||
|
||||
Aktualizovali jsme způsob, jakým zpracováváme frontend URL.
|
||||
Nyní můžete nastavit frontend URL pomocí proměnných `FRONT_DOMAIN`, `FRONT_PROTOCOL` a `FRONT_PORT`.
|
||||
Pokud není FRONT_DOMAIN nastavena, frontend URL se vrátí na `SERVER_URL`.
|
||||
|
||||
### v0.32.0 až v0.33.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.33.0 image
|
||||
|
||||
```
|
||||
yarn command:prod cache:flush
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.33
|
||||
```
|
||||
|
||||
Příkaz `yarn command:prod cache:flush` vyprázdní cache Redis.
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.33` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
Od této verze se obraz twenty-postgres pro DB stal zastaralým a místo něj se používá twenty-postgres-spilo.
|
||||
Pokud chcete pokračovat v používání obrazu twenty-postgres, jednoduše nahraďte `twentycrm/twenty-postgres:${TAG}` za `twentycrm/twenty-postgres` v docker-compose.yml.
|
||||
|
||||
### v0.31.0 až v0.32.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.32.0 image
|
||||
|
||||
**Migrace schématu a dat**
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.32
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.32` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
Aktualizovali jsme způsob, jakým zpracováváme připojení k Redis.
|
||||
|
||||
* Odstraněno: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
|
||||
* Přidáno: `REDIS_URL`
|
||||
|
||||
Aktualizujte svůj `.env` soubor tak, aby používal novou proměnnou `REDIS_URL` místo jednotlivých parametrů připojení k Redis.
|
||||
|
||||
Také jsme zjednodušili způsob, jakým zpracováváme tokeny JWT.
|
||||
|
||||
* Odstraněno: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
|
||||
* Přidáno: `APP_SECRET`
|
||||
|
||||
Aktualizujte svůj `.env` soubor tak, aby používal novou proměnnou `APP_SECRET` místo jednotlivých tokenů (můžete použít stejný tajný řetězec jako dříve nebo vygenerovat nový náhodný řetězec)
|
||||
|
||||
**Propojený účet**
|
||||
|
||||
Pokud používáte propojený účet k synchronizaci vašich emailů a kalendářů Google, budete muset aktivovat [People API](https://developers.google.com/people) na konzoli Google Admin.
|
||||
|
||||
### v0.30.0 až v0.31.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.31.0 image
|
||||
|
||||
**Migrace schématu a dat**:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.31
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.31` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
### v0.24.0 až v0.30.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.30.0 image
|
||||
|
||||
**Změna, která způsobí nekompatibilitu**:
|
||||
Pro zvýšení výkonu nyní vyžaduje Twenty konfiguraci cache Redis. Aktualizovali jsme náš [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml), aby to odrážel.
|
||||
Ujistěte se, že jste aktualizovali svou konfiguraci a své proměnné prostředí odpovídajícím způsobem:
|
||||
|
||||
```
|
||||
REDIS_HOST={váš-redis-host}
|
||||
REDIS_PORT={váš-redis-port}
|
||||
CACHE_STORAGE_TYPE=redis
|
||||
```
|
||||
|
||||
**Migrace schématu a dat**:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.30
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.30` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
### v0.23.0 až v0.24.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.24.0 image
|
||||
|
||||
Spusťte následující příkazy:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.24
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.24` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
### v0.22.0 až v0.23.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.23.0 image
|
||||
|
||||
Spusťte následující příkazy:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.23
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny na databázi.
|
||||
Příkaz `yarn command:prod upgrade-0.23` se postará o datovou migraci, včetně přesunu aktivit na úkoly/poznámky.
|
||||
|
||||
### v0.21.0 až v0.22.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.22.0 image
|
||||
|
||||
Spusťte následující příkazy:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod workspace:sync-metadata -f
|
||||
yarn command:prod upgrade-0.22
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny na databázi.
|
||||
Příkaz `yarn command:prod workspace:sync-metadata -f` synchronizuje definici standardních objektů s tabulkami metadata a aplikuje nezbytné migrace na existující pracovní prostory.
|
||||
Příkaz `yarn command:prod upgrade-0.22` aplikuje specifické datové transformace pro adaptaci na nové objektové defaultRequestInstrumentationOptions.
|
||||
Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.).
|
||||
|
|
|
|||
|
|
@ -1,24 +1,27 @@
|
|||
{
|
||||
"tabs": {
|
||||
"gettingStarted": {
|
||||
"label": "Začínáme",
|
||||
"groups": {
|
||||
"welcome": {
|
||||
"label": "Welcome"
|
||||
},
|
||||
"coreConcepts": {
|
||||
"label": "Core Concepts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"userGuide": {
|
||||
"label": "Uživatelská příručka",
|
||||
"groups": {
|
||||
"discoverTwenty": {
|
||||
"label": "Objevte Twenty",
|
||||
"groups": {
|
||||
"gettingStartedCapabilities": {
|
||||
"label": "Možnosti"
|
||||
},
|
||||
"gettingStartedHowTos": {
|
||||
"label": "Návody"
|
||||
}
|
||||
}
|
||||
"userGuideOverview": {
|
||||
"label": "Přehled"
|
||||
},
|
||||
"dataModel": {
|
||||
"label": "Datový model",
|
||||
"groups": {
|
||||
"dataModelCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"dataModelReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"dataModelHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -28,8 +31,8 @@
|
|||
"dataMigration": {
|
||||
"label": "Migrace dat",
|
||||
"groups": {
|
||||
"dataMigrationCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"dataMigrationReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"dataMigrationHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -39,8 +42,8 @@
|
|||
"calendarEmails": {
|
||||
"label": "Kalendář a e-maily",
|
||||
"groups": {
|
||||
"calendarEmailsCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"calendarEmailsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"calendarEmailsHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -50,8 +53,8 @@
|
|||
"workflows": {
|
||||
"label": "Pracovní postupy",
|
||||
"groups": {
|
||||
"workflowsCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"workflowsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"workflowsHowTos": {
|
||||
"label": "Návody",
|
||||
|
|
@ -75,21 +78,26 @@
|
|||
"ai": {
|
||||
"label": "AI",
|
||||
"groups": {
|
||||
"aiCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"aiReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"aiHowTos": {
|
||||
"label": "Návody"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsPipelines": {
|
||||
"label": "Zobrazení a pipeline",
|
||||
"layout": {
|
||||
"label": "Rozvržení",
|
||||
"groups": {
|
||||
"viewsPipelinesCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"layoutReference": {
|
||||
"label": "Reference",
|
||||
"groups": {
|
||||
"layoutViews": {
|
||||
"label": "Zobrazení"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsPipelinesHowTos": {
|
||||
"layoutHowTos": {
|
||||
"label": "Návody"
|
||||
}
|
||||
}
|
||||
|
|
@ -97,8 +105,8 @@
|
|||
"dashboards": {
|
||||
"label": "Panely",
|
||||
"groups": {
|
||||
"dashboardsCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"dashboardsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"dashboardsHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -108,8 +116,8 @@
|
|||
"permissionsAccess": {
|
||||
"label": "Oprávnění a přístup",
|
||||
"groups": {
|
||||
"permissionsAccessCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"permissionsAccessReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"permissionsAccessHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -119,8 +127,8 @@
|
|||
"billing": {
|
||||
"label": "Fakturace",
|
||||
"groups": {
|
||||
"billingCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"billingReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"billingHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -130,8 +138,8 @@
|
|||
"settings": {
|
||||
"label": "Nastavení",
|
||||
"groups": {
|
||||
"settingsCapabilities": {
|
||||
"label": "Možnosti"
|
||||
"settingsReference": {
|
||||
"label": "Reference"
|
||||
},
|
||||
"settingsHowTos": {
|
||||
"label": "Návody"
|
||||
|
|
@ -143,59 +151,20 @@
|
|||
"developers": {
|
||||
"label": "Vývojáři",
|
||||
"groups": {
|
||||
"developersGroup": {
|
||||
"label": "Vývojáři"
|
||||
"developersOverview": {
|
||||
"label": "Přehled"
|
||||
},
|
||||
"extend": {
|
||||
"label": "Rozšíření",
|
||||
"groups": {
|
||||
"apps": {
|
||||
"label": "Aplikace"
|
||||
}
|
||||
}
|
||||
"apps": {
|
||||
"label": "Aplikace"
|
||||
},
|
||||
"api": {
|
||||
"label": "API"
|
||||
},
|
||||
"selfHost": {
|
||||
"label": "Vlastní hosting",
|
||||
"groups": {
|
||||
"selfHostCapabilities": {
|
||||
"label": "Možnosti"
|
||||
}
|
||||
}
|
||||
"label": "Vlastní hosting"
|
||||
},
|
||||
"contribute": {
|
||||
"label": "Přispět",
|
||||
"groups": {
|
||||
"contributeCapabilities": {
|
||||
"label": "Možnosti",
|
||||
"groups": {
|
||||
"frontendDevelopment": {
|
||||
"label": "Vývoj frontendu",
|
||||
"groups": {
|
||||
"twentyUi": {
|
||||
"label": "Twenty UI",
|
||||
"groups": {
|
||||
"display": {
|
||||
"label": "Zobrazit"
|
||||
},
|
||||
"feedback": {
|
||||
"label": "Zpětná vazba"
|
||||
},
|
||||
"input": {
|
||||
"label": "Vstup"
|
||||
},
|
||||
"navigation": {
|
||||
"label": "Navigace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"backendDevelopment": {
|
||||
"label": "Vývoj backendu"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"label": "Přispět"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Tooltip aplikace
|
||||
icon: zpráva
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Zaškrtnutí
|
||||
icon: circle-check
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Ikony
|
||||
icon: ikony
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
title: Štítek „Brzy“
|
||||
---
|
||||
|
||||
|
||||
Malý odznak nebo "pilulka" označující, že něco brzy přijde.
|
||||
|
||||
```jsx
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Štítek
|
||||
icon: štítek
|
||||
---
|
||||
|
||||
|
||||
Komponenta pro vizuální kategorizaci nebo označení obsahu.
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Tlačítka
|
||||
icon: hand-pointer
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Zaškrtávací políčko
|
||||
icon: square-check
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Barevné schéma
|
||||
icon: paleta
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Rádio
|
||||
icon: circle-dot
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Přepínač
|
||||
icon: toggle-on
|
||||
---
|
||||
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Použití">
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Přehled
|
||||
icon: paleta
|
||||
description: Knihovna komponent pro Twenty CRM
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Navigace
|
||||
icon: compass
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Odkazy
|
||||
icon: odkaz
|
||||
---
|
||||
|
||||
<Frame>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Položka menu
|
||||
icon: bars
|
||||
---
|
||||
|
||||
|
||||
Univerzální položka menu navržená k použití v menu nebo navigačním seznamu.
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue