Compare commits

..
Author SHA1 Message Date
Félix MalfaitandClaude Opus 4.7 85effd6750 fix(billing): gate AI credit-cap at entry points instead of workflow executor
Restores the credit-cap enforcement that was lifted in #19904 (which had
correctly identified that gating per-workflow-step was too coarse), but
moves it to the AI entry points where the actual cost lives.

- agent-async-executor.service: gate executeAgent.
- ai-generate-text.controller: gate the REST handler.
- agent-title-generation.service: gate generateThreadTitle.
- workflow-executor.workspace-service: replace #19904's TODO with an
  absolute-behavior comment documenting why the gate is not here.

Chat resolver was already gated; no change there. Repair-tool-call util
is a sub-call inside an already-gated flow and is left ungated.

Net effect: a workspace that exhausts credits via chat or AI agent stops
making AI calls, but its non-AI workflows (DB CRUD, branching, action
steps) continue running normally.

Addresses the root cause of the 2026-04-26 token-usage incident.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:52:00 +02:00
963 changed files with 31781 additions and 43012 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"mcpServers": {
"postgres": {
"type": "stdio",
"command": "bash",
"args": ["-c", "source packages/twenty-server/.env && npx -y @modelcontextprotocol/server-postgres \"$PG_DATABASE_URL\""],
"env": {}
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["@playwright/mcp@latest", "--no-sandbox", "--headless"],
"env": {}
},
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"],
"env": {}
}
}
}
+223
View File
@@ -0,0 +1,223 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Twenty is an open-source CRM built with modern technologies in a monorepo structure. The codebase is organized as an Nx workspace with multiple packages.
## Key Commands
### Development
```bash
# Start development environment (frontend + backend + worker)
yarn start
# Individual package development
npx nx start twenty-front # Start frontend dev server
npx nx start twenty-server # Start backend server
npx nx run twenty-server:worker # Start background worker
```
### Testing
```bash
# Preferred: run a single test file (fast)
npx jest path/to/test.test.ts --config=packages/PROJECT/jest.config.mjs
# Run all tests for a package
npx nx test twenty-front # Frontend unit tests
npx nx test twenty-server # Backend unit tests
npx nx run twenty-server:test:integration:with-db-reset # Integration tests with DB reset
# To run an indivual test or a pattern of tests, use the following command:
cd packages/{workspace} && npx jest "pattern or filename"
# Storybook
npx nx storybook:build twenty-front
npx nx storybook:test twenty-front
# When testing the UI end to end, click on "Continue with Email" and use the prefilled credentials.
```
### Code Quality
```bash
# Linting (diff with main - fastest, always prefer this)
npx nx lint:diff-with-main twenty-front
npx nx lint:diff-with-main twenty-server
npx nx lint:diff-with-main twenty-front --configuration=fix # Auto-fix
# Linting (full project - slower, use only when needed)
npx nx lint twenty-front
npx nx lint twenty-server
# Type checking
npx nx typecheck twenty-front
npx nx typecheck twenty-server
# Format code
npx nx fmt twenty-front
npx nx fmt twenty-server
```
### Build
```bash
# Build packages (twenty-shared must be built first)
npx nx build twenty-shared
npx nx build twenty-front
npx nx build twenty-server
```
### Database Operations
```bash
# Database management
npx nx database:reset twenty-server # Reset database
npx nx run twenty-server:database:init:prod # Initialize database
npx nx run twenty-server:database:migrate:prod # Run instance commands (fast only)
# Generate an instance command (fast or slow)
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow>
```
### Database Inspection (Postgres MCP)
A read-only Postgres MCP server is configured in `.mcp.json`. Use it to:
- Inspect workspace data, metadata, and object definitions while developing
- Verify migration results (columns, types, constraints) after running migrations
- Explore the multi-tenant schema structure (core, metadata, workspace-specific schemas)
- Debug issues by querying raw data to confirm whether a bug is frontend, backend, or data-level
- Inspect metadata tables to debug GraphQL schema generation issues
This server is read-only — for write operations (reset, migrations, sync), use the CLI commands above.
### GraphQL
```bash
# Generate GraphQL types (run after schema changes)
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
```
## Architecture Overview
### Tech Stack
- **Frontend**: React 18, TypeScript, Jotai (state management), Linaria (styling), Vite
- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL (with GraphQL Yoga)
- **Monorepo**: Nx workspace managed with Yarn 4
### Package Structure
```
packages/
├── twenty-front/ # React frontend application
├── twenty-server/ # NestJS backend API
├── twenty-ui/ # Shared UI components library
├── twenty-shared/ # Common types and utilities
├── twenty-emails/ # Email templates with React Email
├── twenty-website/ # Next.js documentation website
├── twenty-zapier/ # Zapier integration
└── twenty-e2e-testing/ # Playwright E2E tests
```
### Key Development Principles
- **Functional components only** (no class components)
- **Named exports only** (no default exports)
- **Types over interfaces** (except when extending third-party interfaces)
- **String literals over enums** (except for GraphQL enums)
- **No 'any' type allowed** — strict TypeScript enforced
- **Event handlers preferred over useEffect** for state updates
- **Props down, events up** — unidirectional data flow
- **Composition over inheritance**
- **No abbreviations** in variable names (`user` not `u`, `fieldMetadata` not `fm`)
### Naming Conventions
- **Variables/functions**: camelCase
- **Constants**: SCREAMING_SNAKE_CASE
- **Types/Classes**: PascalCase (suffix component props with `Props`, e.g. `ButtonProps`)
- **Files/directories**: kebab-case with descriptive suffixes (`.component.tsx`, `.service.ts`, `.entity.ts`, `.dto.ts`, `.module.ts`)
- **TypeScript generics**: descriptive names (`TData` not `T`)
### File Structure
- Components under 300 lines, services under 500 lines
- Components in their own directories with tests and stories
- Use `index.ts` barrel exports for clean imports
- Import order: external libraries first, then internal (`@/`), then relative
### Comments
- Use short-form comments (`//`), not JSDoc blocks
- Explain WHY (business logic), not WHAT
- Do not comment obvious code
- Multi-line comments use multiple `//` lines, not `/** */`
### State Management
- **Jotai** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- Component-specific state with React hooks (`useState`, `useReducer` for complex logic)
- GraphQL cache managed by Apollo Client
- Use functional state updates: `setState(prev => prev + 1)`
### Backend Architecture
- **NestJS modules** for feature organization
- **TypeORM** for database ORM with PostgreSQL
- **GraphQL** API with code-first approach
- **Redis** for caching and session management
- **BullMQ** for background job processing
### Database & Upgrade Commands
- **PostgreSQL** as primary database
- **Redis** for caching and sessions
- **ClickHouse** for analytics (when enabled)
- When changing entity files, generate an **instance command** (`database:migrate:generate --name <name> --type <fast|slow>`)
- **Fast** instance commands handle schema changes; **slow** ones add a `runDataMigration` step for data backfills
- **Workspace commands** iterate over all active/suspended workspaces for per-workspace upgrades
- Commands use `@RegisteredInstanceCommand` and `@RegisteredWorkspaceCommand` decorators for automatic discovery
- Include both `up` and `down` logic in instance commands
- Never delete or rewrite committed instance command `up`/`down` logic
- See `packages/twenty-server/docs/UPGRADE_COMMANDS.md` for full documentation
### Utility Helpers
Use existing helpers from `twenty-shared` instead of manual type guards:
- `isDefined()`, `isNonEmptyString()`, `isNonEmptyArray()`
## Development Workflow
IMPORTANT: Use Context7 for code generation, setup or configuration steps, or library/API documentation. Automatically use the Context7 MCP tools to resolve library IDs and get library docs without waiting for explicit requests.
### Before Making Changes
1. Always run linting (`lint:diff-with-main`) and type checking after code changes
2. Test changes with relevant test suites (prefer single-file test runs)
3. Ensure instance commands are generated for entity changes (`database:migrate:generate`)
4. Check that GraphQL schema changes are backward compatible
5. Run `graphql:generate` after any GraphQL schema changes
### Code Style Notes
- Use **Linaria** for styling with zero-runtime CSS-in-JS (styled-components pattern)
- Follow **Nx** workspace conventions for imports
- Use **Lingui** for internationalization
- Apply security first, then formatting (sanitize before format)
### Testing Strategy
- **Test behavior, not implementation** — focus on user perspective
- **Test pyramid**: 70% unit, 20% integration, 10% E2E
- Query by user-visible elements (text, roles, labels) over test IDs
- Use `@testing-library/user-event` for realistic interactions
- Descriptive test names: "should [behavior] when [condition]"
- Clear mocks between tests with `jest.clearAllMocks()`
## Dev Environment Setup
All dev environments (Claude Code web, Cursor, local) use one script:
```bash
bash packages/twenty-utils/setup-dev-env.sh
```
This handles everything: starts Postgres + Redis (auto-detects local services vs Docker), creates databases, and copies `.env` files. Idempotent — safe to run multiple times.
- `--docker` — force Docker mode (uses `packages/twenty-docker/docker-compose.dev.yml`)
- `--down` — stop services
- `--reset` — wipe data and restart fresh
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
**Note:** CI workflows (GitHub Actions) manage services via Actions service containers and run setup steps individually — they don't use this script.
## Important Files
- `nx.json` - Nx workspace configuration with task definitions
- `tsconfig.base.json` - Base TypeScript configuration
- `package.json` - Root package with workspace definitions
- `.cursor/rules/` - Detailed development guidelines and best practices
-78
View File
@@ -78,83 +78,6 @@ jobs:
tag: scope:backend
tasks: lint,typecheck
server-previous-version-upgrade-mutation-guard:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Get changed upgrade-version-command files
id: changed-files
uses: tj-actions/changed-files@v45
with:
files: |
packages/twenty-server/src/database/commands/upgrade-version-command/**
- name: Check upgrade version commands are in current version only
if: >
steps.changed-files.outputs.any_changed == 'true' &&
!contains(github.event.pull_request.labels.*.name, 'ci:allow-previous-version-upgrade-mutation')
run: |
VERSION_CONSTANT_FILE="packages/twenty-server/src/engine/core-modules/upgrade/constants/twenty-current-version.constant.ts"
CURRENT_VERSION=$(sed -n "s/.*TWENTY_CURRENT_VERSION = '\([0-9.]*\)'.*/\1/p" "$VERSION_CONSTANT_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "::error::Could not extract TWENTY_CURRENT_VERSION from $VERSION_CONSTANT_FILE"
exit 1
fi
CURRENT_DIR=$(echo "$CURRENT_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+)\..*/\1-\2/')
echo "Current version: $CURRENT_VERSION (directory: $CURRENT_DIR)"
ADDED_OFFENDERS=""
MODIFIED_OFFENDERS=""
check_files() {
local category="$1"
shift
for file in "$@"; do
VERSION_DIR=$(echo "$file" | sed -n 's|.*upgrade-version-command/\([0-9]*-[0-9]*\)/.*|\1|p')
if [ -n "$VERSION_DIR" ] && [ "$VERSION_DIR" != "$CURRENT_DIR" ]; then
if [ "$category" = "added" ]; then
ADDED_OFFENDERS="$ADDED_OFFENDERS\n - $file (version directory: $VERSION_DIR)"
else
MODIFIED_OFFENDERS="$MODIFIED_OFFENDERS\n - $file (version directory: $VERSION_DIR)"
fi
fi
done
}
check_files "added" ${{ steps.changed-files.outputs.added_files }}
check_files "modified" ${{ steps.changed-files.outputs.modified_files }}
if [ -n "$ADDED_OFFENDERS" ] || [ -n "$MODIFIED_OFFENDERS" ]; then
echo "This PR touches upgrade command files outside the current version directory ($CURRENT_DIR / $CURRENT_VERSION)."
if [ -n "$ADDED_OFFENDERS" ]; then
echo ""
echo "New files added to non-current version directories:"
echo -e "$ADDED_OFFENDERS"
fi
if [ -n "$MODIFIED_OFFENDERS" ]; then
echo ""
echo "Existing files modified in non-current version directories:"
echo -e "$MODIFIED_OFFENDERS"
fi
echo ""
echo "If this is intentional, add the label 'ci:allow-previous-version-upgrade-mutation' to this PR and re-run CI."
echo "Otherwise, please move your changes to the current version directory ($CURRENT_DIR)."
echo "::error::Upgrade commands were added or modified in non-current version directories."
exit 1
fi
server-validation:
needs: server-build
timeout-minutes: 30
@@ -388,7 +311,6 @@ jobs:
changed-files-check,
server-build,
server-lint-typecheck,
server-previous-version-upgrade-mutation-guard,
server-validation,
server-test,
server-integration-test,
-2
View File
@@ -58,7 +58,6 @@ jobs:
npx nx run twenty-server:lingui:compile --strict
npx nx run twenty-emails:lingui:compile --strict
npx nx run twenty-front:lingui:compile --strict
npx nx run twenty-website-new:lingui:compile --strict
continue-on-error: true
- name: Stash any changes before pulling translations
@@ -116,7 +115,6 @@ jobs:
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
npx nx run twenty-website-new:lingui:compile
git status
git add .
if ! git diff --staged --quiet --exit-code; then
-2
View File
@@ -41,7 +41,6 @@ jobs:
npx nx run twenty-server:lingui:extract
npx nx run twenty-emails:lingui:extract
npx nx run twenty-front:lingui:extract
npx nx run twenty-website-new:lingui:extract
- name: Check and commit extracted files
id: check_extract_changes
@@ -61,7 +60,6 @@ jobs:
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
npx nx run twenty-website-new:lingui:compile
- name: Check and commit compiled files
id: check_compile_changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "2.1.0",
"version": "2.0.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -1,6 +1,6 @@
{
"name": "github-connector",
"version": "0.2.0",
"version": "0.1.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
@@ -77,7 +77,6 @@ describe('PullRequestReview object', () => {
expect(names).toContain('firstSubmittedAt');
expect(names).toContain('lastSubmittedAt');
expect(names).toContain('eventCount');
expect(names).toContain('isSelfReview');
expect(names).toContain('reviewer');
expect(names).toContain('pullRequest');
expect(names).toContain('reviewEvents');
@@ -82,17 +82,7 @@ describe('verifyGitHubSignature', () => {
});
describe('getRawBodyForSignature', () => {
it('prefers event.rawBody when the runtime forwarded it', () => {
const original = '{ "action": "opened", "number": 42 }';
expect(
getRawBodyForSignature({
body: { action: 'opened', number: 42 },
rawBody: original,
}),
).toBe(original);
});
it('falls back to string body when rawBody is not provided', () => {
it('returns the string as-is for string body', () => {
expect(
getRawBodyForSignature({ body: '{"a":1}', isBase64Encoded: false }),
).toBe('{"a":1}');
@@ -129,22 +119,3 @@ describe('verifyGitHubSignature with parsed body', () => {
});
});
});
describe('end-to-end: server forwards rawBody, signature verifies', () => {
it('verifies a signature when rawBody is present alongside parsed body', () => {
const original = '{ "action": "opened", "number": 42 }';
const event = {
body: { action: 'opened', number: 42 },
rawBody: original,
isBase64Encoded: false,
};
const rawBody = getRawBodyForSignature(event);
const result = verifyGitHubSignature({
rawBody,
signatureHeader: sign(original),
secret: SECRET,
});
expect(result.ok).toBe(true);
});
});
@@ -7,11 +7,7 @@ export type SignatureVerificationResult =
export function getRawBodyForSignature(event: {
body: unknown;
isBase64Encoded?: boolean;
rawBody?: string;
}): string | null {
if (typeof event.rawBody === 'string') {
return event.rawBody;
}
const raw = event.body;
if (raw == null) return '';
if (typeof raw === 'string') {
@@ -298,12 +298,7 @@ const handler = async (
const res = await client.query({
pullRequestReviews: {
__args: {
filter: {
and: [
{ reviewerId: { eq: contributorId } },
{ isSelfReview: { eq: false } },
],
},
filter: { reviewerId: { eq: contributorId } },
orderBy: [{ firstSubmittedAt: 'DescNullsLast' }],
first: PAGE_SIZE,
after: cursor,
@@ -198,7 +198,6 @@ const handler = async (
const res = await client.query({
pullRequestReviews: {
__args: {
filter: { isSelfReview: { eq: false } },
orderBy: [{ firstSubmittedAt: 'DescNullsLast' }],
first: PAGE_SIZE,
after: cursor,
@@ -15,6 +15,5 @@ export async function batchUpsertConsolidatedReviews(
eventCount: true,
reviewerId: true,
pullRequestId: true,
isSelfReview: true,
}) as Promise<PullRequestReviewRow[]>;
}
@@ -23,10 +23,7 @@ type ReviewEventNode = {
reviewerId: string | null;
pullRequestId: string | null;
reviewer: { ghLogin: string | null } | null;
pullRequest: {
githubNumber: number | null;
authorId: string | null;
} | null;
pullRequest: { githubNumber: number | null } | null;
};
type GroupContext = {
@@ -34,7 +31,6 @@ type GroupContext = {
reviewerId: string | null;
prNumber: number | null;
reviewerLogin: string | null;
prAuthorId: string | null;
events: { state: ReviewEventState; submittedAt: string | null }[];
};
@@ -72,7 +68,7 @@ const handler = async (_event: RoutePayload<unknown>) => {
reviewerId: true,
pullRequestId: true,
reviewer: { ghLogin: true },
pullRequest: { githubNumber: true, authorId: true },
pullRequest: { githubNumber: true },
},
},
pageInfo: { hasNextPage: true, endCursor: true },
@@ -99,7 +95,6 @@ const handler = async (_event: RoutePayload<unknown>) => {
reviewerId: node.reviewerId,
prNumber: node.pullRequest?.githubNumber ?? null,
reviewerLogin: node.reviewer?.ghLogin ?? null,
prAuthorId: node.pullRequest?.authorId ?? null,
events: [],
};
groups.set(key, group);
@@ -110,9 +105,6 @@ const handler = async (_event: RoutePayload<unknown>) => {
if (group.prNumber === null && node.pullRequest?.githubNumber != null) {
group.prNumber = node.pullRequest.githubNumber;
}
if (group.prAuthorId === null && node.pullRequest?.authorId) {
group.prAuthorId = node.pullRequest.authorId;
}
group.events.push({
state: node.state,
submittedAt: node.submittedAt,
@@ -131,7 +123,6 @@ const handler = async (_event: RoutePayload<unknown>) => {
prNumber: group.prNumber,
reviewerLogin: group.reviewerLogin,
events: group.events,
prAuthorId: group.prAuthorId,
}),
);
}
@@ -24,9 +24,6 @@ export const REVIEW_EVENT_COUNT_FIELD_UNIVERSAL_IDENTIFIER =
export const PULL_REQUEST_REVIEW_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER =
'b4d61187-1b78-5d6e-9752-79f994ff6d55';
export const REVIEW_IS_SELF_REVIEW_FIELD_UNIVERSAL_IDENTIFIER =
'c1f3e8a2-9b5d-4e7f-8a6c-2d4b6f8e1a93';
enum ReviewState {
APPROVED = 'APPROVED',
CHANGES_REQUESTED = 'CHANGES_REQUESTED',
@@ -119,15 +116,5 @@ export default defineObject({
icon: 'IconHash',
defaultValue: 0,
},
{
universalIdentifier: REVIEW_IS_SELF_REVIEW_FIELD_UNIVERSAL_IDENTIFIER,
name: 'isSelfReview',
type: FieldType.BOOLEAN,
label: 'Self review',
description:
'True when the reviewer is the same contributor as the PR author. Self reviews are excluded from review-count aggregations (top reviewers, contributor stats, etc.) so contributors are credited only for reviews on other people\u2019s PRs.',
icon: 'IconUserCheck',
defaultValue: false,
},
],
});
@@ -8,5 +8,4 @@ export type PullRequestReviewRow = {
eventCount?: number | null;
reviewerId?: string | null;
pullRequestId?: string | null;
isSelfReview?: boolean | null;
};
@@ -12,7 +12,6 @@ export type ConsolidatedReviewUpsertInput = {
eventCount: number;
reviewerId: string | null;
pullRequestId: string;
isSelfReview: boolean;
};
export type BuildConsolidatedRowParams = {
@@ -21,23 +20,8 @@ export type BuildConsolidatedRowParams = {
prNumber: number | null;
reviewerLogin: string | null;
events: ReviewEventForConsolidation[];
/**
* Author of the PR being reviewed. When non-null and equal to `reviewerId`
* the consolidated row is flagged as a self-review so downstream
* aggregations (top reviewers, contributor stats, etc.) can exclude it.
*/
prAuthorId?: string | null;
};
export const isSelfReview = (
reviewerId: string | null,
prAuthorId: string | null | undefined,
): boolean =>
reviewerId !== null &&
prAuthorId !== null &&
prAuthorId !== undefined &&
reviewerId === prAuthorId;
export const buildReviewKey = (
pullRequestId: string,
reviewerId: string | null,
@@ -65,6 +49,5 @@ export const buildConsolidatedRow = (
eventCount: verdict.eventCount,
reviewerId: params.reviewerId,
pullRequestId: params.pullRequestId,
isSelfReview: isSelfReview(params.reviewerId, params.prAuthorId ?? null),
};
};
@@ -8,7 +8,6 @@ import {
buildConsolidatedRow,
buildReviewKey,
buildConsolidatedTitle,
isSelfReview,
} from 'src/modules/github/pull-request-review/utils/build-consolidated-row';
const evt = (
@@ -141,71 +140,6 @@ describe('buildConsolidatedRow', () => {
eventCount: 3,
reviewerId: 'rev-2',
pullRequestId: 'pr-1',
isSelfReview: false,
});
});
it('flags isSelfReview when prAuthorId equals reviewerId', () => {
const row = buildConsolidatedRow({
pullRequestId: 'pr-1',
reviewerId: 'alice',
prNumber: 7,
reviewerLogin: 'alice',
prAuthorId: 'alice',
events: [evt('COMMENTED', '2025-04-01T10:00:00Z')],
});
expect(row.isSelfReview).toBe(true);
});
it('does not flag isSelfReview when prAuthorId differs from reviewerId', () => {
const row = buildConsolidatedRow({
pullRequestId: 'pr-1',
reviewerId: 'alice',
prNumber: 7,
reviewerLogin: 'alice',
prAuthorId: 'bob',
events: [evt('APPROVED', '2025-04-01T10:00:00Z')],
});
expect(row.isSelfReview).toBe(false);
});
it('does not flag isSelfReview when prAuthorId is missing', () => {
const row = buildConsolidatedRow({
pullRequestId: 'pr-1',
reviewerId: 'alice',
prNumber: 7,
reviewerLogin: 'alice',
events: [evt('APPROVED', '2025-04-01T10:00:00Z')],
});
expect(row.isSelfReview).toBe(false);
});
it('does not flag isSelfReview when reviewerId is null (ghost reviewer)', () => {
const row = buildConsolidatedRow({
pullRequestId: 'pr-1',
reviewerId: null,
prNumber: 7,
reviewerLogin: null,
prAuthorId: null,
events: [evt('COMMENTED', '2025-04-01T10:00:00Z')],
});
expect(row.isSelfReview).toBe(false);
});
});
describe('isSelfReview', () => {
it('returns true only when both ids are present and equal', () => {
expect(isSelfReview('a', 'a')).toBe(true);
});
it('returns false when ids differ', () => {
expect(isSelfReview('a', 'b')).toBe(false);
});
it('returns false when either side is null/undefined', () => {
expect(isSelfReview(null, 'a')).toBe(false);
expect(isSelfReview('a', null)).toBe(false);
expect(isSelfReview('a', undefined)).toBe(false);
expect(isSelfReview(null, null)).toBe(false);
});
});
@@ -110,16 +110,9 @@ const handler = async (event: RoutePayload<FetchPrsPayload>) => {
reviewerId: string | null;
prNumber: number;
reviewerLogin: string | null;
prAuthorId: string | null;
events: { state: ReviewEventState; submittedAt: string | null }[];
};
const authorIdByPullRequestId = new Map<string, string | null>();
for (const pr of prData) {
const id = prIdByNumber.get(pr.githubNumber);
if (id) authorIdByPullRequestId.set(id, pr.authorId);
}
let skippedReviews = 0;
const reviewEventData: ReviewEventInput[] = [];
const groups = new Map<GroupKey, GroupContext>();
@@ -148,7 +141,6 @@ const handler = async (event: RoutePayload<FetchPrsPayload>) => {
reviewerId,
prNumber: pr.number,
reviewerLogin: review.author?.login ?? null,
prAuthorId: authorIdByPullRequestId.get(pullRequestId) ?? null,
events: [],
};
groups.set(key, group);
@@ -185,7 +177,6 @@ const handler = async (event: RoutePayload<FetchPrsPayload>) => {
prNumber: group.prNumber,
reviewerLogin: group.reviewerLogin,
events: group.events,
prAuthorId: group.prAuthorId,
}),
);
const consolidatedRecords = await timed(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "2.1.0",
"version": "2.0.0",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -3193,7 +3193,6 @@ type Mutation {
updateView(id: String!, input: UpdateViewInput!): View!
deleteView(id: String!): Boolean!
destroyView(id: String!): Boolean!
upsertViewWidget(input: UpsertViewWidgetInput!): View!
createViewSort(input: CreateViewSortInput!): ViewSort!
updateViewSort(input: UpdateViewSortInput!): ViewSort!
deleteViewSort(input: DeleteViewSortInput!): Boolean!
@@ -3511,59 +3510,6 @@ input UpdateViewInput {
shouldHideEmptyGroups: Boolean
}
input UpsertViewWidgetInput {
"""The id of the view widget (page layout widget)."""
widgetId: UUID!
"""The view fields to upsert."""
viewFields: [UpsertViewWidgetViewFieldInput!]
"""The view filters to upsert."""
viewFilters: [UpsertViewWidgetViewFilterInput!]
"""The view filter groups to upsert."""
viewFilterGroups: [UpsertViewWidgetViewFilterGroupInput!]
"""The view sorts to upsert."""
viewSorts: [UpsertViewWidgetViewSortInput!]
}
input UpsertViewWidgetViewFieldInput {
"""The id of an existing view field to update."""
viewFieldId: UUID
"""
The field metadata id. Used to create a new view field when viewFieldId is not provided.
"""
fieldMetadataId: UUID
isVisible: Boolean!
position: Float!
size: Float
}
input UpsertViewWidgetViewFilterInput {
id: UUID
fieldMetadataId: UUID!
operand: ViewFilterOperand = CONTAINS
value: JSON!
viewFilterGroupId: UUID
positionInViewFilterGroup: Float
subFieldName: String
}
input UpsertViewWidgetViewFilterGroupInput {
id: UUID
parentViewFilterGroupId: UUID
logicalOperator: ViewFilterGroupLogicalOperator = AND
positionInViewFilterGroup: Float
}
input UpsertViewWidgetViewSortInput {
id: UUID
fieldMetadataId: UUID!
direction: ViewSortDirection = ASC
}
input CreateViewSortInput {
id: UUID
fieldMetadataId: UUID!
@@ -2675,7 +2675,6 @@ export interface Mutation {
updateView: View
deleteView: Scalars['Boolean']
destroyView: Scalars['Boolean']
upsertViewWidget: View
createViewSort: ViewSort
updateViewSort: ViewSort
deleteViewSort: Scalars['Boolean']
@@ -5726,7 +5725,6 @@ export interface MutationGenqlSelection{
updateView?: (ViewGenqlSelection & { __args: {id: Scalars['String'], input: UpdateViewInput} })
deleteView?: { __args: {id: Scalars['String']} }
destroyView?: { __args: {id: Scalars['String']} }
upsertViewWidget?: (ViewGenqlSelection & { __args: {input: UpsertViewWidgetInput} })
createViewSort?: (ViewSortGenqlSelection & { __args: {input: CreateViewSortInput} })
updateViewSort?: (ViewSortGenqlSelection & { __args: {input: UpdateViewSortInput} })
deleteViewSort?: { __args: {input: DeleteViewSortInput} }
@@ -5946,30 +5944,6 @@ export interface CreateViewInput {id?: (Scalars['UUID'] | null),name: Scalars['S
export interface UpdateViewInput {id?: (Scalars['UUID'] | null),name?: (Scalars['String'] | null),type?: (ViewType | null),icon?: (Scalars['String'] | null),position?: (Scalars['Float'] | null),isCompact?: (Scalars['Boolean'] | null),openRecordIn?: (ViewOpenRecordIn | null),kanbanAggregateOperation?: (AggregateOperations | null),kanbanAggregateOperationFieldMetadataId?: (Scalars['UUID'] | null),anyFieldFilterValue?: (Scalars['String'] | null),calendarLayout?: (ViewCalendarLayout | null),calendarFieldMetadataId?: (Scalars['UUID'] | null),visibility?: (ViewVisibility | null),mainGroupByFieldMetadataId?: (Scalars['UUID'] | null),shouldHideEmptyGroups?: (Scalars['Boolean'] | null)}
export interface UpsertViewWidgetInput {
/** The id of the view widget (page layout widget). */
widgetId: Scalars['UUID'],
/** The view fields to upsert. */
viewFields?: (UpsertViewWidgetViewFieldInput[] | null),
/** The view filters to upsert. */
viewFilters?: (UpsertViewWidgetViewFilterInput[] | null),
/** The view filter groups to upsert. */
viewFilterGroups?: (UpsertViewWidgetViewFilterGroupInput[] | null),
/** The view sorts to upsert. */
viewSorts?: (UpsertViewWidgetViewSortInput[] | null)}
export interface UpsertViewWidgetViewFieldInput {
/** The id of an existing view field to update. */
viewFieldId?: (Scalars['UUID'] | null),
/** The field metadata id. Used to create a new view field when viewFieldId is not provided. */
fieldMetadataId?: (Scalars['UUID'] | null),isVisible: Scalars['Boolean'],position: Scalars['Float'],size?: (Scalars['Float'] | null)}
export interface UpsertViewWidgetViewFilterInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],operand?: (ViewFilterOperand | null),value: Scalars['JSON'],viewFilterGroupId?: (Scalars['UUID'] | null),positionInViewFilterGroup?: (Scalars['Float'] | null),subFieldName?: (Scalars['String'] | null)}
export interface UpsertViewWidgetViewFilterGroupInput {id?: (Scalars['UUID'] | null),parentViewFilterGroupId?: (Scalars['UUID'] | null),logicalOperator?: (ViewFilterGroupLogicalOperator | null),positionInViewFilterGroup?: (Scalars['Float'] | null)}
export interface UpsertViewWidgetViewSortInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],direction?: (ViewSortDirection | null)}
export interface CreateViewSortInput {id?: (Scalars['UUID'] | null),fieldMetadataId: Scalars['UUID'],direction?: (ViewSortDirection | null),viewId: Scalars['UUID']}
export interface UpdateViewSortInput {
File diff suppressed because it is too large Load Diff
@@ -11,12 +11,10 @@ COPY ./nx.json .
COPY ./.yarn/releases /app/.yarn/releases
COPY ./.yarn/patches /app/.yarn/patches
COPY ./packages/twenty-oxlint-rules /app/packages/twenty-oxlint-rules
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/package.json
COPY ./packages/twenty-website-new/package.json /app/packages/twenty-website-new/package.json
RUN yarn
COPY ./packages/twenty-shared /app/packages/twenty-shared
COPY ./packages/twenty-website-new /app/packages/twenty-website-new
RUN npx nx build twenty-website-new
+7
View File
@@ -49,6 +49,8 @@ RUN yarn workspaces focus --production twenty-emails twenty-shared twenty-sdk tw
FROM common-deps AS twenty-front-build
ARG REACT_APP_SERVER_BASE_URL
COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-front-component-renderer /app/packages/twenty-front-component-renderer
COPY ./packages/twenty-ui /app/packages/twenty-ui
@@ -136,6 +138,9 @@ USER 1000
FROM twenty-server AS twenty
ARG REACT_APP_SERVER_BASE_URL
ENV REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL
COPY --chown=1000 --from=twenty-front-build /app/packages/twenty-front/build /app/packages/twenty-server/dist/front
LABEL org.opencontainers.image.description="Twenty image with backend and frontend."
@@ -227,6 +232,7 @@ RUN mkdir -p /data/postgres /data/redis /app/packages/twenty-server/.local-stora
&& chown -R postgres:postgres /data/postgres \
&& chown 1000:1000 /data/redis /app/packages/twenty-server/.local-storage
ARG REACT_APP_SERVER_BASE_URL
ARG APP_VERSION=0.0.0
ENV S6_KEEP_ENV=1
@@ -235,6 +241,7 @@ ENV PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/default \
REDIS_URL=redis://localhost:6379 \
STORAGE_TYPE=local \
APP_SECRET=twenty-app-dev-secret-not-for-production \
REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL \
APP_VERSION=$APP_VERSION \
NODE_ENV=development \
NODE_PORT=2020 \
@@ -101,7 +101,6 @@ The `RoutePayload` type has the following structure:
| `queryStringParameters` | `Record<string, string \| undefined>` | Query string parameters (multiple values joined with commas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }`|
| `pathParameters` | `Record<string, string \| undefined>` | Path parameters extracted from the route pattern | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Parsed request body (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Original UTF-8 request body, before JSON parsing. Useful for verifying HMAC-style webhook signatures (e.g. GitHub's `X-Hub-Signature-256`, Stripe). `undefined` when the runtime did not preserve it. | |
| `isBase64Encoded` | `boolean` | Whether the body is base64 encoded | |
| `requestContext.http.method` | `string` | HTTP method (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Raw request path | |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

@@ -94,16 +94,15 @@ const handler = async (event: RoutePayload) => {
يحتوي نوع `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' }` |
| `body` | `object \| null` | جسم الطلب المُحلَّل (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Original UTF-8 request body, before JSON parsing. Useful for verifying HMAC-style webhook signatures (e.g. GitHub's `X-Hub-Signature-256`, Stripe). `undefined` when the runtime did not preserve it. | |
| `isBase64Encoded` | `boolean` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | |
| `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | المسار الخام للطلب | |
| الخاصية | النوع | الوصف | مثال |
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `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' }` |
| `body` | `object \| null` | جسم الطلب المُحلَّل (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | |
| `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | المسار الخام للطلب | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ description: تعرّف على خطط تسعير Twenty وكيفية التبد
* دعم قياسي
<Note>
Premium features (SSO, row-level permissions and AI usage data) are not included in the Pro plan.
الميزات المتميزة (SSO وأذونات على مستوى الصف) غير مشمولة في خطة Pro.
</Note>
### المؤسسة (سحابي)
@@ -27,7 +27,7 @@ Premium features (SSO, row-level permissions and AI usage data) are not included
للفرق الأكبر ذات الاحتياجات المتقدّمة:
* كل ما في Pro
* **Premium features**: SSO integration, row-level permissions and AI usage data
* **ميزات متميزة**: تكامل SSO وأذونات على مستوى الصف
* دعم متميز
## خطط الاستضافة الذاتية
@@ -45,7 +45,7 @@ Premium features (SSO, row-level permissions and AI usage data) are not included
للفرق التي تحتاج إلى ميزات متميزة أثناء الاستضافة الذاتية:
* جميع ميزات Pro
* **Premium features**: SSO integration, row-level permissions and AI usage data
* **ميزات متميزة**: تكامل SSO وأذونات على مستوى الصف
* دعم فريق Twenty
* لا يُشترط نشر الشيفرة المخصّصة كمفتوح المصدر قبل التوزيع
@@ -55,7 +55,6 @@ Premium features (SSO, row-level permissions and AI usage data) are not included
* **تكامل SSO**: تسجيل دخول أحادي مع موفّر الهوية لديك
* **أذونات على مستوى الصف**: تحكّم دقيق في الوصول على مستوى السجل
* **AI usage data**: Track AI consumption across the workspace
## التبديل بين الخطط
@@ -78,15 +77,3 @@ Premium features (SSO, row-level permissions and AI usage data) are not included
### التبديل إلى الفوترة الشهرية
تواصل مع الدعم للعودة إلى الفوترة الشهرية.
## Obtain an Enterprise Key for Organization (Self-Hosted)
To use the Organization (Self-Hosted) plan, you need to obtain an Enterprise key:
1. Go to **Settings → Admin Panel → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Enterprise key" />
2. Click **Get Enterprise Key**
3. When you are redirected to Stripe, enter your payment details and confirm
4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
@@ -16,11 +16,6 @@ description: الأسئلة الشائعة حول تسعير Twenty والفوت
لا تتوفر الميزات المتميزة إلا في خطط Organization (السحابة أو الاستضافة الذاتية):
* **تكامل SSO**: تسجيل الدخول الأحادي مع موفر الهوية لديك
* **أذونات على مستوى السجل**: تحكم دقيق في الوصول على مستوى السجل
* **بيانات استخدام الذكاء الاصطناعي**: تتبع استهلاك الذكاء الاصطناعي عبر مساحة العمل
</Accordion>
<Accordion title="هل خطة Organization على السحابة وخطة Organization ذاتية الاستضافة متماثلتان؟">
كلتاهما تقدمان الميزات المتميزة نفسها. ومع ذلك، لا يمكن استخدام اشتراك السحابة لعمليات النشر ذاتية الاستضافة. يتطلب النشر ذاتي الاستضافة مفتاح Enterprise.
</Accordion>
<Accordion title="هل تقدمون مقاعد مجانية للمستخدمين العارضين فقط؟">
@@ -53,45 +53,38 @@ People ←→ Project Assignments ←→ Projects
1. اذهب إلى **الإعدادات → نموذج البيانات**
2. انقر **+ كائن جديد**
3. سمِّه تسمية وصفية (مثلًا: "تعيين مشروع"، "عضو فريق"، "طلب منتج")
4. فعِّل خيار "تخطي إنشاء حقل الاسم"
<img src="/images/user-guide/fields/new-pivot-object.png" alt="كائن ربط جديد" />
5. انقر على **حفظ**
4. انقر على **حفظ**
<Tip>
**اتفاقية التسمية**: استخدم اسمًا يصف العلاقة، مثل "تعيين مشروع" أو "عضوية الفريق". هذا يجعل نموذج البيانات أسهل في الفهم.
</Tip>
## الخطوة 2: إنشاء علاقات بين الكائنات وكائن الربط
## الخطوة 2: إنشاء علاقات من كائن الربط
أضف حقول العلاقة من كلٍ من الكائنين لديك إلى كائن الربط.
أضِف حقول علاقة من كائن الربط إلى كلا الكائنين اللذين تريد ربطهما.
### العلاقة الأولى (الكائن A → كائن الربط)
1. حدِّد الكائن الأول لديك في **الإعدادات → نموذج البيانات**
2. انقر **+ إضافة علاقة**
3. اختر كائن الربط (مثلًا، "تعيينات المشروع")
4. عيِّن نوع العلاقة إلى **واحد-إلى-متعدد** (يمكن لشخص واحد الارتباط بالعديد من التعيينات)
5. قم بتسمية الحقول:
* الحقل على الأشخاص: مثلًا، "تعيينات المشروع"
* الحقل على كائن الربط: مثلًا، "شخص"
6. انقر على **حفظ**
### العلاقة الثانية (الكائن B → كائن الربط)
1. حدِّد الكائن الثاني لديك في **الإعدادات → نموذج البيانات**
2. انقر **+ إضافة علاقة**
3. اختر كائن الربط (مثلًا، "تعيينات المشروع")
4. عيِّن نوع العلاقة إلى **واحد-إلى-متعدد** (يمكن لمشروع واحد الارتباط بالعديد من التعيينات)
5. فعّل **"هذه علاقة بكائن ربط"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### العلاقة الأولى (كائن الربطالكائن A)
1. حدِّد كائن الربط في **الإعدادات → نموذج البيانات**
2. انقر **+ إضافة حقل**
3. اختر **العلاقة** كنوع الحقل
4. اختر الكائن الأول (مثلًا، "الأشخاص")
5. عيِّن نوع العلاقة إلى **متعدد-إلى-واحد** (يمكن لعديد من التعيينات الارتباط بشخص واحد)
6. قم بتسمية الحقول:
* الحقل على كائن الربط: مثلًا، "شخص"
* الحقل على الأشخاص: مثلًا، "تعيينات المشروع"
7. انقر على **حفظ**
### العلاقة الثانية (كائن الربط → الكائن B)
1. وأنت ما زلت في كائن الربط، انقر **+ إضافة حقل**
2. اختر **العلاقة** كنوع الحقل
3. اختر الكائن الثاني (مثلًا، "المشاريع")
4. عيِّن نوع العلاقة إلى **متعدد-إلى-واحد**
5. قم بتسمية الحقول:
* الحقل على كائن الربط: مثلًا، "مشروع"
* الحقل على المشاريع: مثلًا، "أعضاء الفريق"
7. انقر على **حفظ**
6. انقر على **حفظ**
## الخطوة 3: ضبط عرض علاقة الربط
@@ -105,6 +98,18 @@ People ←→ Project Assignments ←→ Projects
6. حدِّد **العلاقة الهدف** (مثلًا، "مشروع" — الحقل على كائن الربط الذي يشير إلى الجانب الآخر)
7. انقر على **حفظ**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
كرِّر على الكائن الآخر:
1. اختر "المشاريع" في نموذج البيانات
2. حرِّر حقل العلاقة "أعضاء الفريق"
3. فعّل مفتاح الربط
4. حدِّد "شخص" كالعلاقة الهدف
5. حفظ
## النتيجة
بعد التكوين:
@@ -125,15 +130,15 @@ People ←→ Project Assignments ←→ Projects
### إضافة علاقات
1. **الأشخاص → تعيين مشروع**
* النوع: واحد-إلى-متعدد
* الحقل على الأشخاص: "تعيينات المشروع"
1. **تعيين مشروع → الأشخاص**
* النوع: متعدد-إلى-واحد
* الحقل على التعيين: "شخص"
* الحقل على الأشخاص: "تعيينات المشروع"
2. **المشاريع → تعيين مشروع**
* النوع: واحد-إلى-متعدد
* الحقل على المشاريع: "أعضاء الفريق"
2. **تعيين مشروع → المشاريع**
* النوع: متعدد-إلى-واحد
* الحقل على التعيين: "مشروع"
* الحقل على المشاريع: "أعضاء الفريق"
### ضبط عرض علاقة الربط
@@ -30,9 +30,3 @@ description: خصص الشريط الجانبي الأيسر ليتوافق مع
## قائمة الأوامر
اضغط `Cmd+K` (أو `Ctrl+K`) لفتح قائمة الأوامر — شريط بحث للوصول السريع يتيح لك الانتقال إلى أي سجل أو طريقة عرض أو إجراء دون التنقل عبر الشريط الجانبي.
## تخصيص الشريط الجانبي
لتخصيص الشريط الجانبي، مرّر المؤشر فوق قسم "مساحة العمل" في الشريط الجانبي وانقر على أيقونة مفتاح الربط.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="أيقونة تحرير التنقّل" />
@@ -3,8 +3,6 @@ title: صفحات السجل
description: خصص تخطيط صفحات تفاصيل السجلات باستخدام علامات تبويب وأدوات.
---
## نظرة عامة
عند فتح سجل في Twenty، تتكون صفحة التفاصيل من **علامات تبويب** و**أدوات**. كلاهما قابل للتخصيص بالكامل حسب نوع الكائن.
## علامات التبويب
@@ -40,20 +38,12 @@ description: خصص تخطيط صفحات تفاصيل السجلات باستخ
1. افتح أي سجل
2. اضغط على `Cmd+K` وابحث عن "تحرير تخطيط صفحة السجل"
أو
1. انتقل إلى الإعدادات > نموذج البيانات > الكائن الذي تختاره > التخطيط
2. انقر على الزر "تخصيص صفحة السجل" لذلك الكائن
3. أنت الآن في وضع التخصيص:
* **أضف أدوات** من منتقي الأدوات
* **اسحب الأدوات** لإعادة وضعها على الشبكة
* **غيّر حجم الأدوات** بسحب حوافها
* **اضبط الحقول** المعروضة داخل كل أداة
* **أدِر علامات التبويب** — أضف، أزل، أعد التسمية، وأعد الترتيب
4. احفظ تغييراتك — سيتم تطبيقها على جميع السجلات لذلك النوع من الكائنات
## ظهور الحقول
@@ -95,16 +95,15 @@ const handler = async (event: RoutePayload) => {
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 }` |
| `rawBody` | `string \| undefined` | Original UTF-8 request body, before JSON parsing. Useful for verifying HMAC-style webhook signatures (e.g. GitHub's `X-Hub-Signature-256`, Stripe). `undefined` when the runtime did not preserve it. | |
| `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 | |
| 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
@@ -19,7 +19,7 @@ Pro týmy připravené škálovat:
* Standardní podpora
<Note>
Premium features (SSO, row-level permissions and AI usage data) are not included in the Pro plan.
Prémiové funkce (SSO a oprávnění na úrovni řádků) nejsou součástí plánu Pro.
</Note>
### Organizace (Cloud)
@@ -27,7 +27,7 @@ Premium features (SSO, row-level permissions and AI usage data) are not included
Pro větší týmy s pokročilými potřebami:
* Vše z plánu Pro
* **Premium features**: SSO integration, row-level permissions and AI usage data
* **Prémiové funkce**: integrace SSO a oprávnění na úrovni řádků
* Prioritní podpora
## Plány pro selfhosting
@@ -45,7 +45,7 @@ Hostujte Twenty na vlastní infrastruktuře bez poplatků:
Pro týmy, které při selfhostingu potřebují prémiové funkce:
* Všechny funkce plánu Pro
* **Premium features**: SSO integration, row-level permissions and AI usage data
* **Prémiové funkce**: integrace SSO a oprávnění na úrovni řádků
* Podpora týmu Twenty
* Není vyžadováno zveřejnit vlastní kód jako opensource před distribucí
@@ -55,7 +55,6 @@ Prémiové funkce jsou dostupné pouze v plánech Organizace (Cloud nebo self
* **Integrace SSO**: jednotné přihlášení s vaším poskytovatelem identity
* **Oprávnění na úrovni řádků**: jemně odstupňované řízení přístupu na úrovni záznamu
* **AI usage data**: Track AI consumption across the workspace
## Přepínání plánů
@@ -78,15 +77,3 @@ Chcete-li přejít na nižší plán, kontaktujte podporu.
### Přepnout na měsíční fakturaci
Chcete-li přepnout zpět na měsíční fakturaci, kontaktujte podporu.
## Obtain an Enterprise Key for Organization (Self-Hosted)
To use the Organization (Self-Hosted) plan, you need to obtain an Enterprise key:
1. Go to **Settings → Admin Panel → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Enterprise key" />
2. Click **Get Enterprise Key**
3. When you are redirected to Stripe, enter your payment details and confirm
4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
@@ -16,11 +16,6 @@ Pokud chcete hostovat sami a potřebujete prémiové funkce (SSO a oprávnění
Prémiové funkce jsou dostupné pouze v plánech Organization (Cloud nebo Self-Hosted):
* **Integrace SSO**: Jednotné přihlášení s vaším poskytovatelem identity
* **Oprávnění na úrovni řádků**: Jemně granulované řízení přístupu na úrovni záznamu
* **AI usage data**: Track AI consumption across the workspace
</Accordion>
<Accordion title="Is Organization plan on cloud and Organization plan on self-hosted the same?">
They offer the same premium features. However, a cloud subscription cannot be used for self-hosted deployments. Self-hosted requires an Enterprise key.
</Accordion>
<Accordion title="Nabízíte volná místa pro uživatele pouze s přístupem ke čtení?">
@@ -53,45 +53,38 @@ Nejprve vytvořte mezilehlý objekt, který bude uchovávat propojení.
1. Přejděte do **Nastavení → Datový model**
2. Klikněte na **+ New object**
3. Pojmenujte jej výstižně (např. "Project Assignment", "Team Member", "Product Order")
4. Přepněte "Přeskočit vytvoření pole Název" na zapnuto
<img src="/images/user-guide/fields/new-pivot-object.png" alt="Nový spojovací objekt" />
5. Klikněte na **Uložit**
4. Klikněte na **Uložit**
<Tip>
**Konvence pojmenování**: Použijte název, který popisuje vztah, například "Project Assignment" nebo "Team Membership". Tím bude datový model srozumitelnější.
</Tip>
## Krok 2: Vytvořte vztahy mezi objekty a spojovacím objektem
## Krok 2: Vytvořte vztahy ze spojovacího objektu
Přidejte vztahová pole z obou objektů do spojovacího objektu.
Přidejte relační pole ze spojovacího objektu do obou objektů, které chcete propojit.
### První vztah (Objekt A → Junction)
1. Vyberte svůj první objekt v **Settings → Data Model**
2. Klikněte na **+ Přidat vztah**
3. Vyberte spojovací objekt (např. "Project Assignments")
4. Nastavte typ vztahu na **One-To-Many** (jedna osoba může být propojena s mnoha přiřazeními)
5. Pojmenujte pole:
* Pole na People: např. "Project Assignments"
* Pole na spojovacím objektu: např. "Person"
6. Klikněte na **Uložit**
### Druhý vztah (Objekt B → Junction)
1. Vyberte svůj druhý objekt v **Settings → Data Model**
2. Klikněte na **+ Přidat vztah**
3. Vyberte spojovací objekt (např. "Project Assignments")
4. Nastavte typ vztahu na **One-To-Many** (jeden projekt může být propojen s mnoha přiřazeními)
5. Povolte **"This is a relation to a Junction Object"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### První vztah (Junction → Objekt A)
1. Vyberte svůj spojovací objekt v **Settings → Data Model**
2. Klikněte na **+ Add Field**
3. Zvolte **Relation** jako typ pole
4. Vyberte první objekt (např. "People")
5. Nastavte typ vztahu na **Many-to-One** (mnoho přiřazení může odkazovat na jednu osobu)
6. Pojmenujte pole:
* Pole na spojovacím objektu: např. "Person"
* Pole na People: např. "Project Assignments"
7. Klikněte na **Uložit**
### Druhý vztah (Junction → Objekt B)
1. Stále na spojovacím objektu klikněte na **+ Add Field**
2. Zvolte **Relation** jako typ pole
3. Vyberte druhý objekt (např. "Projects")
4. Nastavte typ vztahu na **Many-to-One**
5. Pojmenujte pole:
* Pole na spojovacím objektu: např. "Project"
* Pole na Projects: např. "Team Members"
7. Klikněte na **Uložit**
6. Klikněte na **Uložit**
## Krok 3: Nakonfigurujte zobrazení vztahu přes spojovací objekt
@@ -105,6 +98,18 @@ Nyní nakonfigurujte zdrojové objekty tak, aby zobrazovaly propojené záznamy
6. Vyberte **Target relation** (např. "Project" — pole na spojovacím objektu, které ukazuje na druhou stranu)
7. Klikněte na **Uložit**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Opakujte pro druhý objekt:
1. Vyberte "Projects" v Data Model
2. Upravte relační pole "Team Members"
3. Povolte přepínač pro vztah přes spojovací objekt
4. Vyberte "Person" jako cílový vztah
5. Uložit
## Výsledek
Po konfiguraci:
@@ -125,15 +130,15 @@ Zde je kompletní postup:
### Přidejte vztahy
1. **People → Project Assignment**
* Typ: One-to-Many
* Pole na People: "Project Assignments"
1. **Project Assignment → People**
* Typ: Many-to-One
* Pole na Assignment: "Person"
* Pole na People: "Project Assignments"
2. **Projects → Project Assignment**
* Typ: One-to-Many
* Pole na Projects: "Team Members"
2. **Project Assignment → Projects**
* Typ: Many-to-One
* Pole na Assignment: "Project"
* Pole na Projects: "Team Members"
### Nakonfigurujte zobrazení spojovacího objektu
@@ -30,9 +30,3 @@ Přidejte odkazy na externí nástroje přímo do postranního panelu. Užitečn
## Nabídka příkazů
Stiskněte `Cmd+K` (nebo `Ctrl+K`) pro otevření nabídky příkazů — rychlá vyhledávací lišta pro přechod na libovolný záznam, zobrazení nebo akci bez procházení postranního panelu.
## Přizpůsobení postranního panelu
Chcete-li přizpůsobit postranní panel, najeďte myší na sekci "Pracovní prostor" v postranním panelu a klikněte na ikonu klíče.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Ikona pro úpravu navigace" />
@@ -3,8 +3,6 @@ title: Stránky záznamů
description: Přizpůsobte rozvržení jednotlivých stránek detailu záznamu pomocí karet a widgetů.
---
## Přehled
Když v Twenty otevřete záznam, stránka detailu se skládá z **karet** a **widgetů**. Obojí lze pro každý typ objektu plně přizpůsobit.
## Karty
@@ -40,20 +38,12 @@ Widgety jsou stavební prvky uvnitř každé karty. Mezi dostupné typy widgetů
1. Otevřete libovolný záznam
2. Stiskněte `Cmd+K` a vyhledejte "Upravit rozložení stránky záznamu"
nebo
1. Přejděte do Nastavení > Datový model > objekt dle vašeho výběru > Rozložení
2. Klikněte na tlačítko "Přizpůsobit stránku záznamu" pro daný objekt
3. Nyní jste v režimu přizpůsobení:
* **Přidávejte widgety** z výběru widgetů
* **Přetahujte widgety** pro jejich přemístění v mřížce
* **Změňte velikost widgetů** tažením jejich okrajů
* **Nakonfigurujte pole** zobrazovaná v jednotlivých widgetech
* **Spravujte karty** — přidávejte, odebírejte, přejmenovávejte, měňte pořadí
4. Uložte změny — budou platit pro všechny záznamy daného typu objektu
## Viditelnost polí
@@ -94,16 +94,15 @@ const handler = async (event: RoutePayload) => {
Der Typ `RoutePayload` hat die folgende Struktur:
| Eigenschaft | Typ | Beschreibung | Beispiel |
| ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | siehe Abschnitt unten |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Aus dem Routenmuster extrahierte Pfadparameter | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Geparster Request-Body (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Ursprünglicher UTF-8-Request-Body vor dem JSON-Parsing. Nützlich zur Verifizierung von Webhook-Signaturen im HMAC-Stil (z. B. GitHubs `X-Hub-Signature-256`, Stripe). `undefined`, wenn die Laufzeitumgebung es nicht beibehalten hat. | |
| `isBase64Encoded` | `boolean` | Gibt an, ob der Body Base64-codiert ist | |
| `requestContext.http.method` | `Zeichenkette` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `Zeichenkette` | Rohpfad der Anfrage | |
| Eigenschaft | Typ | Beschreibung | Beispiel |
| ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | siehe Abschnitt unten |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Aus dem Routenmuster extrahierte Pfadparameter | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Geparster Request-Body (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | Gibt an, ob der Body Base64-codiert ist | |
| `requestContext.http.method` | `Zeichenkette` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `Zeichenkette` | Rohpfad der Anfrage | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ Für Teams, die bereit sind zu skalieren:
* Standard-Support
<Note>
Premiumfunktionen (SSO, Berechtigungen auf Zeilenebene und KI-Nutzungsdaten) sind im Pro-Tarif nicht enthalten.
Premiumfunktionen (SSO und Berechtigungen auf Zeilenebene) sind im Pro-Plan nicht enthalten.
</Note>
### Organisation (Cloud)
@@ -27,7 +27,7 @@ Premiumfunktionen (SSO, Berechtigungen auf Zeilenebene und KI-Nutzungsdaten) sin
Für größere Teams mit erweiterten Anforderungen:
* Alles aus Pro
* **Premiumfunktionen**: SSO-Integration, Berechtigungen auf Zeilenebene und KI-Nutzungsdaten
* **Premiumfunktionen**: SSO-Integration und Berechtigungen auf Zeilenebene
* Vorrangiger Support
## Selbstgehostete Pläne
@@ -45,7 +45,7 @@ Hosten Sie Twenty auf Ihrer eigenen Infrastruktur kostenlos:
Für Teams, die beim Selbsthosting Premiumfunktionen benötigen:
* Alle Pro-Funktionen
* **Premiumfunktionen**: SSO-Integration, Berechtigungen auf Zeilenebene und KI-Nutzungsdaten
* **Premiumfunktionen**: SSO-Integration und Berechtigungen auf Zeilenebene
* Support durch das Twenty-Team
* Keine Verpflichtung, benutzerdefinierten Code vor der Weitergabe als Open Source zu veröffentlichen
@@ -55,7 +55,6 @@ Premiumfunktionen sind nur in den Organisation-Plänen (Cloud oder Selbstgehoste
* **SSO-Integration**: Single Sign-On mit Ihrem Identitätsanbieter
* **Berechtigungen auf Zeilenebene**: Feingranulare Zugriffskontrolle auf Datensatzebene
* **KI-Nutzungsdaten**: Verfolgen Sie den KI-Verbrauch in Ihrem Arbeitsbereich
## Pläne wechseln
@@ -78,15 +77,3 @@ Wenden Sie sich an den Support, um Ihren Plan herabzustufen.
### Zur monatlichen Abrechnung wechseln
Wenden Sie sich an den Support, um wieder zur monatlichen Abrechnung zu wechseln.
## Enterprise-Schlüssel für Organization (Self-Hosted) abrufen
Um den Tarif Organization (Self-Hosted) zu verwenden, müssen Sie einen Enterprise-Schlüssel abrufen:
1. Gehen Sie zu **Einstellungen → Admin-Panel → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Enterprise-Schlüssel" />
2. Klicken Sie auf **Enterprise-Schlüssel abrufen**
3. Wenn Sie zu Stripe weitergeleitet werden, geben Sie Ihre Zahlungsdaten ein und bestätigen Sie
4. Wenn Ihr Enterprise-Schlüssel angezeigt wird, fügen Sie ihn auf der Seite Enterprise-Einstellungen ein und aktivieren Sie die Organization-Lizenz
@@ -16,11 +16,6 @@ Wenn Sie selbst hosten möchten und die Premium-Funktionen (SSO und Berechtigung
Premium-Funktionen sind nur in den Organization-Tarifen (Cloud oder Self-Hosted) verfügbar:
* **SSO-Integration**: Single Sign-On mit Ihrem Identitätsanbieter
* **Berechtigungen auf Zeilenebene**: feingranulare Zugriffskontrolle auf Datensatzebene
* **KI-Nutzungsdaten**: Verfolgen Sie den KI-Verbrauch in Ihrem Arbeitsbereich
</Accordion>
<Accordion title="Sind der Organisationsplan in der Cloud und der Organisationsplan bei selbst gehosteter Bereitstellung gleich?">
Sie bieten dieselben Premium-Funktionen. Ein Cloud-Abonnement kann jedoch nicht für selbst gehostete Bereitstellungen verwendet werden. Für selbst gehostete Bereitstellungen ist ein Enterprise-Schlüssel erforderlich.
</Accordion>
<Accordion title="Bieten Sie kostenlose Plätze für Nur-Ansicht-Benutzer an?">
@@ -53,45 +53,38 @@ Erstellen Sie zunächst das Zwischenobjekt, das die Verknüpfungen speichert.
1. Gehen Sie zu **Einstellungen → Datenmodell**
2. Klicken Sie auf **+ Neues Objekt**
3. Benennen Sie es aussagekräftig (z. B. "Projektzuweisung", "Teammitglied", "Produktbestellung")
4. Schalten Sie "Erstellen eines Namensfelds überspringen" ein
<img src="/images/user-guide/fields/new-pivot-object.png" alt="Neues Verknüpfungsobjekt" />
5. Klicken Sie auf **Speichern**
4. Klicken Sie auf **Speichern**
<Tip>
**Namenskonvention**: Verwenden Sie einen Namen, der die Beziehung beschreibt, z. B. "Projektzuweisung" oder "Teammitgliedschaft". Das macht das Datenmodell leichter verständlich.
</Tip>
## Schritt 2: Beziehungen zwischen Objekten und dem Verknüpfungsobjekt erstellen
## Schritt 2: Beziehungen vom Verknüpfungsobjekt erstellen
Fügen Sie für jedes Ihrer beiden Objekte Beziehungsfelder zum Verknüpfungsobjekt hinzu.
Fügen Sie vom Verknüpfungsobjekt aus Beziehungsfelder zu beiden Objekten hinzu, die Sie verbinden möchten.
### Erste Beziehung (Objekt A → Verknüpfung)
1. Wählen Sie Ihr erstes Objekt unter **Einstellungen → Datenmodell** aus
2. Klicken Sie auf **+ Beziehung hinzufügen**
3. Wählen Sie das Verknüpfungsobjekt aus (z. B. "Projektzuweisungen")
4. Legen Sie den Beziehungstyp auf **Eins-zu-Viele** fest (eine Person kann mit vielen Zuweisungen verknüpft werden)
5. Benennen Sie die Felder:
* Feld bei Personen: z. B. "Projektzuweisungen"
* Feld auf der Verknüpfung: z. B. "Person"
6. Klicken Sie auf **Speichern**
### Zweite Beziehung (Objekt B → Verknüpfung)
1. Wählen Sie Ihr zweites Objekt unter **Einstellungen → Datenmodell** aus
2. Klicken Sie auf **+ Beziehung hinzufügen**
3. Wählen Sie das Verknüpfungsobjekt aus (z. B. "Projektzuweisungen")
4. Legen Sie den Beziehungstyp auf **Eins-zu-Viele** fest (ein Projekt kann mit vielen Zuweisungen verknüpft werden)
5. Aktivieren Sie **"Dies ist eine Beziehung zu einem Verknüpfungsobjekt"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### Erste Beziehung (Verknüpfung → Objekt A)
1. Wählen Sie Ihr Verknüpfungsobjekt unter **Einstellungen → Datenmodell** aus
2. Klicken Sie auf **+ Feld hinzufügen**
3. Wählen Sie **Relation** als Feldtyp
4. Wählen Sie das erste Objekt aus (z. B. "Personen")
5. Legen Sie den Beziehungstyp auf **Viele-zu-Eins** fest (viele Zuweisungen können mit einer Person verknüpft werden)
6. Benennen Sie die Felder:
* Feld auf der Verknüpfung: z. B. "Person"
* Feld bei Personen: z. B. "Projektzuweisungen"
7. Klicken Sie auf **Speichern**
### Zweite Beziehung (Verknüpfung → Objekt B)
1. Bleiben Sie im Verknüpfungsobjekt und klicken Sie auf **+ Feld hinzufügen**
2. Wählen Sie **Relation** als Feldtyp
3. Wählen Sie das zweite Objekt aus (z. B. "Projekte")
4. Legen Sie den Beziehungstyp auf **Viele-zu-Eins** fest
5. Benennen Sie die Felder:
* Feld auf der Verknüpfung: z. B. "Projekt"
* Feld bei Projekten: z. B. "Teammitglieder"
7. Klicken Sie auf **Speichern**
6. Klicken Sie auf **Speichern**
## Schritt 3: Anzeige der Verknüpfungsbeziehung konfigurieren
@@ -105,6 +98,18 @@ Konfigurieren Sie nun die Quellobjekte so, dass verknüpfte Datensätze direkt a
6. Wählen Sie die **Zielbeziehung** aus (z. B. "Projekt" — das Feld an der Verknüpfung, das auf die andere Seite zeigt)
7. Klicken Sie auf **Speichern**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Wiederholen Sie dies für das andere Objekt:
1. Wählen Sie "Projekte" im Datenmodell aus
2. Bearbeiten Sie das Beziehungsfeld "Teammitglieder"
3. Aktivieren Sie den Verknüpfungsschalter
4. Wählen Sie "Person" als Zielbeziehung aus
5. Speichern
## Ergebnis
Nach der Konfiguration:
@@ -125,15 +130,15 @@ Hier ist eine vollständige Schritt-für-Schritt-Anleitung:
### Beziehungen hinzufügen
1. **Personen → Projektzuweisung**
* Typ: Eins-zu-Viele
* Feld bei Personen: "Projektzuweisungen"
1. **Projektzuweisung → Personen**
* Typ: Viele-zu-Eins
* Feld bei Zuweisung: "Person"
* Feld bei Personen: "Projektzuweisungen"
2. **Projekte → Projektzuweisung**
* Typ: Eins-zu-Viele
* Feld bei Projekten: "Teammitglieder"
2. **Projektzuweisung → Projekte**
* Typ: Viele-zu-Eins
* Feld bei Zuweisung: "Projekt"
* Feld bei Projekten: "Teammitglieder"
### Verknüpfungsanzeige konfigurieren
@@ -30,9 +30,3 @@ Fügen Sie Links zu externen Tools direkt in der Seitenleiste hinzu. Nützlich,
## Befehlsmenü
Drücken Sie `Cmd+K` (oder `Ctrl+K`), um das Befehlsmenü zu öffnen — eine Suchleiste für den Schnellzugriff, mit der Sie zu jedem Datensatz, jeder Ansicht oder Aktion springen können, ohne durch die Seitenleiste zu navigieren.
## Anpassung der Seitenleiste
Um die Seitenleiste anzupassen, bewegen Sie den Mauszeiger über den Abschnitt "Arbeitsbereich" in der Seitenleiste und klicken Sie auf das Schraubenschlüssel-Symbol.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Symbol zum Bearbeiten der Navigation" />
@@ -3,8 +3,6 @@ title: Datensatzseiten
description: Passen Sie das Layout einzelner Datensatz-Detailseiten mit Tabs und Widgets an.
---
## Übersicht
Wenn Sie in Twenty einen Datensatz öffnen, besteht die Detailseite aus **Tabs** und **Widgets**. Beide lassen sich je Objekttyp vollständig anpassen.
## Registerkarten
@@ -40,20 +38,12 @@ Widgets sind die Bausteine in jedem Tab. Zu den verfügbaren Widget-Typen gehör
1. Öffnen Sie einen beliebigen Datensatz
2. Drücken Sie `Cmd+K` und suchen Sie nach "Layout der Datensatzseite bearbeiten"
oder
1. Gehen Sie zu Einstellungen > Datenmodell > Objekt Ihrer Wahl > Layout
2. Klicken Sie für dieses Objekt auf die Schaltfläche "Datensatzseite anpassen"
3. Sie befinden sich jetzt im Anpassungsmodus:
* **Widgets hinzufügen** aus der Widget-Auswahl
* **Widgets ziehen**, um sie im Raster neu zu positionieren
* **Widgets in der Größe ändern**, indem Sie ihre Ränder ziehen
* **Felder konfigurieren**, die in jedem Widget angezeigt werden
* **Tabs verwalten** — hinzufügen, entfernen, umbenennen, neu anordnen
4. Speichern Sie Ihre Änderungen — sie gelten für alle Datensätze dieses Objekttyps
## Feldsichtbarkeit
@@ -94,16 +94,15 @@ const handler = async (event: RoutePayload) => {
Il tipo `RoutePayload` ha la seguente struttura:
| Proprietà | Tipo | Descrizione | Esempio |
| ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | vedi la sezione sotto |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Parametri della query string (valori multipli uniti da virgole) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Parametri di percorso estratti dal pattern della route | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Corpo della richiesta analizzato (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Original UTF-8 request body, before JSON parsing. Useful for verifying HMAC-style webhook signatures (e.g. GitHub's `X-Hub-Signature-256`, Stripe). `undefined` when the runtime did not preserve it. | |
| `isBase64Encoded` | `boolean` | Indica se il corpo è codificato in base64 | |
| `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | |
| Proprietà | Tipo | Descrizione | Esempio |
| ---------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | vedi la sezione sotto |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Parametri della query string (valori multipli uniti da virgole) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Parametri di percorso estratti dal pattern della route | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Corpo della richiesta analizzato (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | Indica se il corpo è codificato in base64 | |
| `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ Per i team pronti a scalare:
* Supporto standard
<Note>
Premium features (SSO, row-level permissions and AI usage data) are not included in the Pro plan.
Le funzionalità premium (SSO e autorizzazioni a livello di riga) non sono incluse nel piano Pro.
</Note>
### Organizzazione (Cloud)
@@ -27,7 +27,7 @@ Premium features (SSO, row-level permissions and AI usage data) are not included
Per i team più numerosi con esigenze avanzate:
* Tutto ciò che è incluso in Pro
* **Premium features**: SSO integration, row-level permissions and AI usage data
* **Funzionalità premium**: integrazione SSO e autorizzazioni a livello di riga
* Supporto prioritario
## Piani selfhosted
@@ -45,7 +45,7 @@ Esegui Twenty sulla tua infrastruttura senza costi:
Per i team che necessitano di funzionalità premium con il selfhosting:
* Tutte le funzionalità di Pro
* **Premium features**: SSO integration, row-level permissions and AI usage data
* **Funzionalità premium**: integrazione SSO e autorizzazioni a livello di riga
* Supporto del team di Twenty
* Nessun requisito di pubblicare il codice personalizzato come open source prima della distribuzione
@@ -55,7 +55,6 @@ Le funzionalità premium sono disponibili solo nei piani Organizzazione (Cloud o
* **Integrazione SSO**: Single SignOn con il tuo provider di identità
* **Autorizzazioni a livello di riga**: controllo degli accessi granulare a livello di record
* **AI usage data**: Track AI consumption across the workspace
## Cambio piano
@@ -78,15 +77,3 @@ Contatta il supporto per eseguire il downgrade del tuo piano.
### Passa alla fatturazione mensile
Contatta il supporto per tornare alla fatturazione mensile.
## Obtain an Enterprise Key for Organization (Self-Hosted)
To use the Organization (Self-Hosted) plan, you need to obtain an Enterprise key:
1. Go to **Settings → Admin Panel → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Enterprise key" />
2. Click **Get Enterprise Key**
3. When you are redirected to Stripe, enter your payment details and confirm
4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
@@ -16,11 +16,6 @@ Se vuoi effettuare il self-hosting e hai bisogno delle funzionalità Premium (SS
Le funzionalità Premium sono disponibili solo nei piani Organization (Cloud o Self-Hosted):
* **Integrazione SSO**: Single Sign-On con il tuo provider di identità
* **Autorizzazioni a livello di riga**: controllo degli accessi granulare a livello di record
* **Dati di utilizzo dell'IA**: Tieni traccia del consumo dell'IA nel tuo spazio di lavoro
</Accordion>
<Accordion title="Il piano Organization su cloud e il piano Organization in modalità self-hosted sono la stessa cosa?">
Offrono le stesse funzionalità Premium. Tuttavia, un abbonamento cloud non può essere utilizzato per distribuzioni self-hosted. La modalità self-hosted richiede una chiave Enterprise.
</Accordion>
<Accordion title="Offrite posti gratuiti per utenti solo visualizzazione?">
@@ -53,45 +53,38 @@ Per prima cosa, crea l'oggetto intermedio che conterrà i collegamenti.
1. Vai a **Impostazioni → Modello dati**
2. Fai clic su **+ Nuovo oggetto**
3. Assegnagli un nome descrittivo (ad es., "Assegnazione al progetto", "Membro del team", "Ordine del prodotto")
4. Attiva l'opzione "Salta la creazione di un campo Nome"
<img src="/images/user-guide/fields/new-pivot-object.png" alt="Nuovo oggetto pivot" />
5. Clicca su **Salva**
4. Clicca su **Salva**
<Tip>
**Convenzione di denominazione**: Usa un nome che descriva la relazione, come "Assegnazione al progetto" o "Appartenenza al team". Questo rende il modello di dati più facile da comprendere.
</Tip>
## Passaggio 2: Crea relazioni tra gli oggetti e l'oggetto di giunzione
## Passaggio 2: Crea le relazioni dall'oggetto di giunzione
Aggiungi campi di relazione da ciascuno dei due oggetti all'oggetto di giunzione.
Aggiungi campi di relazione dall'oggetto di giunzione a entrambi gli oggetti che desideri collegare.
### Prima relazione (Oggetto A → Oggetto di giunzione)
1. Seleziona il tuo primo oggetto in **Impostazioni → Modello dati**
2. Fai clic su **+ Aggiungi relazione**
3. Seleziona l'oggetto di giunzione (ad es., "Assegnazioni al progetto")
4. Imposta il tipo di relazione su **Uno-a-molti** (una persona può collegarsi a molte assegnazioni)
5. Assegna un nome ai campi:
* Campo su Persone: ad es., "Assegnazioni ai progetti"
* Campo sull'oggetto di giunzione: ad es., "Persona"
6. Clicca su **Salva**
### Seconda relazione (Oggetto B → Oggetto di giunzione)
1. Seleziona il tuo secondo oggetto in **Impostazioni → Modello dati**
2. Fai clic su **+ Aggiungi relazione**
3. Seleziona l'oggetto di giunzione (ad es., "Assegnazioni al progetto")
4. Imposta il tipo di relazione su **Uno-a-molti** (un progetto può collegarsi a molte assegnazioni)
5. Abilita **"Questa è una relazione con un oggetto di giunzione"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### Prima relazione (Giunzione → Oggetto A)
1. Seleziona il tuo oggetto di giunzione in **Impostazioni → Modello dati**
2. Fai clic su **+ Aggiungi campo**
3. Scegli **Relazione** come tipo di campo
4. Seleziona il primo oggetto (ad es., "Persone")
5. Imposta il tipo di relazione su **Molti-a-uno** (molte assegnazioni possono collegarsi a una persona)
6. Assegna un nome ai campi:
* Campo sull'oggetto di giunzione: ad es., "Persona"
* Campo su Persone: ad es., "Assegnazioni ai progetti"
7. Clicca su **Salva**
### Seconda relazione (Giunzione → Oggetto B)
1. Sempre sull'oggetto di giunzione, fai clic su **+ Aggiungi campo**
2. Scegli **Relazione** come tipo di campo
3. Seleziona il secondo oggetto (ad es., "Progetti")
4. Imposta il tipo di relazione su **Molti-a-uno**
5. Assegna un nome ai campi:
* Campo sull'oggetto di giunzione: ad es., "Progetto"
* Campo su Progetti: ad es., "Membri del team"
7. Clicca su **Salva**
6. Clicca su **Salva**
## Passaggio 3: Configura la visualizzazione della relazione di giunzione
@@ -105,6 +98,18 @@ Ora configura gli oggetti sorgente per visualizzare direttamente i record colleg
6. Seleziona la **Relazione di destinazione** (ad es., "Progetto" — il campo sull'oggetto di giunzione che punta all'altro lato)
7. Clicca su **Salva**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Ripeti per l'altro oggetto:
1. Seleziona "Progetti" in Modello dati
2. Modifica il campo di relazione "Membri del team"
3. Abilita l'interruttore di giunzione
4. Seleziona "Persona" come relazione di destinazione
5. Salva
## Risultato
Dopo la configurazione:
@@ -125,15 +130,15 @@ Ecco una procedura completa:
### Aggiungi relazioni
1. **Persone → Assegnazione al progetto**
* Tipo: Uno-a-molti
* Campo su Persone: "Assegnazioni ai progetti"
1. **Assegnazione al progetto → Persone**
* Tipo: Molti-a-uno
* Campo su Assegnazione: "Persona"
* Campo su Persone: "Assegnazioni ai progetti"
2. **Progetti → Assegnazione al progetto**
* Tipo: Uno-a-molti
* Campo su Progetti: "Membri del team"
2. **Assegnazione al progetto → Progetti**
* Tipo: Molti-a-uno
* Campo su Assegnazione: "Progetto"
* Campo su Progetti: "Membri del team"
### Configura la visualizzazione della giunzione
@@ -30,9 +30,3 @@ Aggiungi collegamenti a strumenti esterni direttamente nella barra laterale. Uti
## Menu Comandi
Premi `Cmd+K` (o `Ctrl+K`) per aprire il menu comandi — una barra di ricerca ad accesso rapido per passare a qualsiasi record, vista o azione senza usare la barra laterale.
## Personalizzare la barra laterale
Per personalizzare la barra laterale, passa il mouse sulla sezione "Workspace" nella barra laterale e fai clic sull'icona a forma di chiave inglese.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Icona di modifica della navigazione" />
@@ -3,8 +3,6 @@ title: Pagine dei record
description: Personalizza il layout delle singole pagine di dettaglio dei record con schede e widget.
---
## Panoramica
Quando apri un record in Twenty, la pagina di dettaglio è composta da **schede** e **widget**. Entrambi sono completamente personalizzabili per ciascun tipo di oggetto.
## Schede
@@ -40,20 +38,12 @@ I widget sono gli elementi costitutivi all'interno di ogni scheda. Tipi di widge
1. Apri un record qualsiasi
2. Premi `Cmd+K` e cerca "Modifica il layout della pagina del record"
o
1. Vai a Impostazioni > Modello dati > oggetto di tua scelta > Layout
2. Fai clic sul pulsante "Personalizza pagina del record" per quell'oggetto
3. Ora sei in modalità di personalizzazione:
* **Aggiungi widget** dal selettore di widget
* **Trascina i widget** per riposizionarli sulla griglia
* **Ridimensiona i widget** trascinando i bordi
* **Configura i campi** mostrati all'interno di ciascun widget
* **Gestisci le schede** — aggiungi, rimuovi, rinomina, riordina
4. Salva le modifiche — si applicano a tutti i record di quel tipo di oggetto
## Visibilità dei campi
@@ -94,16 +94,15 @@ const handler = async (event: RoutePayload) => {
O tipo `RoutePayload` tem a seguinte estrutura:
| Propriedade | Tipo | Descrição | Exemplo |
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | veja a seção abaixo |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Parâmetros de caminho extraídos do padrão de rota | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Corpo da requisição analisado (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Corpo da solicitação UTF-8 original, antes da análise de JSON. Útil para verificar assinaturas de webhook no estilo HMAC (por exemplo, `X-Hub-Signature-256` do GitHub, Stripe). `undefined` quando o ambiente de execução não o preservou. | |
| `isBase64Encoded` | `boolean` | Se o corpo está codificado em base64 | |
| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Caminho bruto da requisição | |
| Propriedade | Tipo | Descrição | Exemplo |
| ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | veja a seção abaixo |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Parâmetros de caminho extraídos do padrão de rota | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Corpo da requisição analisado (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | Se o corpo está codificado em base64 | |
| `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Caminho bruto da requisição | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ Para equipes prontas para escalar:
* Suporte padrão
<Note>
Os recursos Premium (SSO, permissões em nível de linha e dados de uso de IA) não estão incluídos no plano Pro.
Recursos premium (SSO e permissões em nível de linha) não estão incluídos no plano Pro.
</Note>
### Organização (Nuvem)
@@ -27,7 +27,7 @@ Os recursos Premium (SSO, permissões em nível de linha e dados de uso de IA) n
Para equipes maiores com necessidades avançadas:
* Tudo do Pro
* **Recursos Premium**: integração com SSO, permissões em nível de linha e dados de uso de IA
* **Recursos premium**: integração SSO e permissões em nível de linha
* Suporte prioritário
## Planos auto-hospedados
@@ -45,7 +45,7 @@ Hospede o Twenty na sua própria infraestrutura sem custo:
Para equipes que precisam de recursos premium enquanto se auto-hospedam:
* Todos os recursos do Pro
* **Recursos Premium**: integração com SSO, permissões em nível de linha e dados de uso de IA
* **Recursos premium**: integração SSO e permissões em nível de linha
* Suporte da equipe Twenty
* Sem necessidade de publicar código personalizado como código aberto antes de distribuir
@@ -55,7 +55,6 @@ Os recursos premium estão disponíveis apenas nos planos Organização (Nuvem o
* **Integração SSO**: Single Sign-On com seu provedor de identidade
* **Permissões em nível de linha**: controle de acesso granular no nível de registro
* **Dados de uso de IA**: Acompanhe o consumo de IA em todo o espaço de trabalho
## Mudança de planos
@@ -78,15 +77,3 @@ Entre em contato com o suporte para fazer downgrade do seu plano.
### Mudar para faturamento mensal
Entre em contato com o suporte para voltar ao faturamento mensal.
## Obtenha uma chave Enterprise para Organization (Self-Hosted)
Para usar o plano Organization (Self-Hosted), você precisa obter uma chave Enterprise:
1. Vá para **Configurações → Painel de Administração → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Chave Enterprise" />
2. Clique em **Obter chave Enterprise**
3. Quando você for redirecionado ao Stripe, insira seus dados de pagamento e confirme
4. Quando sua chave Enterprise for exibida, cole-a na página de configurações do Enterprise e ative a licença do Organization
@@ -16,11 +16,6 @@ Se você quiser hospedar por conta própria e precisar dos recursos Premium (SSO
Os recursos Premium estão disponíveis apenas nos planos Organization (Cloud ou Self-Hosted):
* **Integração de SSO**: Single Sign-On com seu provedor de identidade
* **Permissões em nível de linha**: Controle de acesso granular no nível de registro
* **Dados de uso de IA**: Acompanhe o consumo de IA em todo o espaço de trabalho
</Accordion>
<Accordion title="O plano Organization na nuvem e o plano Organization em ambiente autohospedado são iguais?">
Ambos oferecem os mesmos recursos Premium. No entanto, uma assinatura na nuvem não pode ser usada para implantações autohospedadas. O ambiente autohospedado requer uma chave Enterprise.
</Accordion>
<Accordion title="Você oferece assentos gratuitos para usuários apenas visualizadores?">
@@ -53,45 +53,38 @@ Primeiro, crie o objeto intermediário que manterá as conexões.
1. Vá a **Definições → Modelo de Dados**
2. Clique em **+ Novo objeto**
3. Dê um nome descritivo (por exemplo, "Atribuição de Projeto", "Membro da Equipe", "Pedido de Produto")
4. Ative a opção "Ignorar a criação de um campo Nome"
<img src="/images/user-guide/fields/new-pivot-object.png" alt="Novo objeto pivô" />
5. Clique em **Salvar**
4. Clique em **Salvar**
<Tip>
**Convenção de nomenclatura**: Use um nome que descreva a relação, como "Atribuição de Projeto" ou "Participação na Equipe". Isso torna o modelo de dados mais fácil de entender.
</Tip>
## Etapa 2: Crie relações entre os objetos e o objeto de junção
## Etapa 2: Criar Relações a partir do Objeto de Junção
Adicione campos de relação de cada um dos seus dois objetos ao objeto de junção.
Adicione campos de relação do objeto de junção para ambos os objetos que deseja conectar.
### Primeira Relação (Objeto A → Junção)
1. Selecione seu primeiro objeto em **Settings → Data Model**
2. Clique em **+ Adicionar relação**
3. Selecione o objeto de junção (por exemplo, "Atribuições de Projeto")
4. Defina o tipo de relação como **Um-para-muitos** (uma pessoa pode se vincular a muitas atribuições)
5. Nomeie os campos:
* Campo em Pessoas: por exemplo, "Atribuições de Projeto"
* Campo na junção: por exemplo, "Pessoa"
6. Clique em **Salvar**
### Segunda Relação (Objeto B → Junção)
1. Selecione seu segundo objeto em **Settings → Data Model**
2. Clique em **+ Adicionar relação**
3. Selecione o objeto de junção (por exemplo, "Atribuições de Projeto")
4. Defina o tipo de relação como **Um-para-muitos** (um projeto pode se vincular a muitas atribuições)
5. Ative **"Esta é uma relação com um objeto de junção"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### Primeira Relação (Junção → Objeto A)
1. Selecione seu objeto de junção em **Settings → Data Model**
2. Clique em **+ Adicionar campo**
3. Escolha **Relação** como o tipo de campo
4. Selecione o primeiro objeto (por exemplo, "Pessoas")
5. Defina o tipo de relação como **Muitos-para-um** (muitas atribuições podem se vincular a uma pessoa)
6. Nomeie os campos:
* Campo na junção: por exemplo, "Pessoa"
* Campo em Pessoas: por exemplo, "Atribuições de Projeto"
7. Clique em **Salvar**
### Segunda Relação (Junção → Objeto B)
1. Ainda no objeto de junção, clique em **+ Add Field**
2. Escolha **Relação** como o tipo de campo
3. Selecione o segundo objeto (por exemplo, "Projetos")
4. Defina o tipo de relação como **Muitos-para-um**
5. Nomeie os campos:
* Campo na junção: por exemplo, "Projeto"
* Campo em Projetos: por exemplo, "Membros da Equipe"
7. Clique em **Salvar**
6. Clique em **Salvar**
## Etapa 3: Configurar a Exibição da Relação de Junção
@@ -105,6 +98,18 @@ Agora configure os objetos de origem para exibir diretamente os registros vincul
6. Selecione a **Relação de destino** (por exemplo, "Projeto" — o campo na junção que aponta para o outro lado)
7. Clique em **Salvar**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Repita para o outro objeto:
1. Selecione "Projetos" em Data Model
2. Edite o campo de relação "Membros da Equipe"
3. Ative o alternador de junção
4. Selecione "Pessoa" como a relação de destino
5. Salvar
## Resultado
Após a configuração:
@@ -125,15 +130,15 @@ Aqui está um passo a passo completo:
### Adicionar Relações
1. **Pessoas → Atribuição de Projeto**
* Tipo: Um-para-muitos
* Campo em Pessoas: "Atribuições de Projeto"
1. **Atribuição de Projeto → Pessoas**
* Tipo: Muitos-para-um
* Campo em Atribuição: "Pessoa"
* Campo em Pessoas: "Atribuições de Projeto"
2. **Projetos → Atribuição de Projeto**
* Tipo: Um-para-muitos
* Campo em Projetos: "Membros da Equipe"
2. **Atribuição de Projeto → Projetos**
* Tipo: Muitos-para-um
* Campo em Atribuição: "Projeto"
* Campo em Projetos: "Membros da Equipe"
### Configurar Exibição de Junção
@@ -30,9 +30,3 @@ Adicione links para ferramentas externas diretamente na barra lateral. Útil par
## Menu de Comandos
Pressione `Cmd+K` (ou `Ctrl+K`) para abrir o menu de comandos — uma barra de pesquisa de acesso rápido para ir a qualquer registro, visualização ou ação sem navegar pela barra lateral.
## Personalizando a barra lateral
Para personalizar a barra lateral, passe o cursor sobre a seção "Workspace" na barra lateral e clique no ícone de chave inglesa.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Ícone de edição da navegação" />
@@ -3,8 +3,6 @@ title: Páginas de Registos
description: Personalize o layout das páginas de detalhe de registos individuais com abas e widgets.
---
## Visão Geral
Ao abrir um registo no Twenty, a página de detalhe é composta por **abas** e **widgets**. Ambos são totalmente personalizáveis por tipo de objeto.
## Abas
@@ -40,20 +38,12 @@ Os widgets são os componentes básicos dentro de cada aba. Tipos de widget disp
1. Abra qualquer registro
2. Pressione `Cmd+K` e pesquise por "Edit record page layout"
ou
1. Vá para Configurações > Modelo de dados > objeto de sua escolha > Layout
2. Clique no botão "Personalizar página de registro" para esse objeto
3. Agora você está no modo de personalização:
* **Adicionar widgets** no seletor de widgets
* **Arraste widgets** para reposicioná-los na grade
* **Redimensione os widgets** arrastando suas bordas
* **Configure campos** exibidos em cada widget
* **Gerencie abas** — adicione, remova, renomeie, reordene
4. Salve suas alterações — elas se aplicam a todos os registros desse tipo de objeto
## Visibilidade de campos
@@ -94,16 +94,15 @@ const handler = async (event: RoutePayload) => {
Тип `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' }` |
| `body` | `object \| null` | Разобранное тело запроса (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Исходное тело запроса в кодировке UTF-8, до разбора JSON. Полезно для проверки подписей вебхуков в стиле HMAC (например, `X-Hub-Signature-256` от GitHub, Stripe). `undefined`, если среда выполнения не сохранила его. | |
| `isBase64Encoded` | `boolean` | Является ли тело закодированным в base64 | |
| `requestContext.http.method` | `string` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Необработанный путь запроса | |
| Свойство | Тип | Описание | Пример |
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `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' }` |
| `body` | `object \| null` | Разобранное тело запроса (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | Является ли тело закодированным в base64 | |
| `requestContext.http.method` | `string` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Необработанный путь запроса | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ Twenty предлагает гибкие тарифы для команд люб
* Стандартная поддержка
<Note>
Премиальные функции (SSO, разрешения на уровне строк и данные об использовании ИИ) не включены в тариф Pro.
Премиальные функции (SSO и разрешения на уровне записей) не входят в план Pro.
</Note>
### Организация (облако)
@@ -27,7 +27,7 @@ Twenty предлагает гибкие тарифы для команд люб
Для крупных команд с расширенными потребностями:
* Все, что есть в Pro
* **Премиальные функции**: интеграция SSO, разрешения на уровне строк и данные об использовании ИИ
* **Премиальные функции**: интеграция с SSO и разрешения на уровне записей
* Приоритетная поддержка
## Планы для самостоятельного размещения
@@ -45,7 +45,7 @@ Twenty предлагает гибкие тарифы для команд люб
Для команд, которым нужны премиальные функции при самостоятельном размещении:
* Все возможности Pro
* **Премиальные функции**: интеграция SSO, разрешения на уровне строк и данные об использовании ИИ
* **Премиальные функции**: интеграция с SSO и разрешения на уровне записей
* Поддержка от команды Twenty
* Не требуется публиковать пользовательский код с открытым исходным кодом перед распространением
@@ -55,7 +55,6 @@ Twenty предлагает гибкие тарифы для команд люб
* **Интеграция с SSO**: единый вход через вашего поставщика идентификации
* **Разрешения на уровне записей**: тонкая настройка управления доступом на уровне записей
* **Данные об использовании ИИ**: Отслеживайте потребление ИИ в рабочем пространстве
## Переключение планов
@@ -78,15 +77,3 @@ Twenty предлагает гибкие тарифы для команд люб
### Переход на ежемесячную оплату
Свяжитесь со службой поддержки, чтобы вернуться на ежемесячную оплату.
## Получите ключ Enterprise для организации (самостоятельный хостинг)
Чтобы использовать тариф Organization (Self-Hosted), необходимо получить ключ Enterprise:
1. Перейдите в **Настройки → Панель администратора → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Ключ Enterprise" />
2. Нажмите **Получить ключ Enterprise**
3. Когда вы будете перенаправлены на Stripe, введите платёжные данные и подтвердите
4. Когда ваш ключ Enterprise будет отображён, вставьте его на странице настроек Enterprise и активируйте лицензию Organization
@@ -16,11 +16,6 @@ description: Часто задаваемые вопросы о тарифах и
Премиум-функции доступны только в планах Organization (облако или Self-Hosted):
* **Интеграция с SSO**: единый вход с вашим провайдером идентификации
* **Разрешения на уровне записей**: детализированное управление доступом на уровне записей
* **Данные об использовании ИИ**: Отслеживайте потребление ИИ в рабочем пространстве
</Accordion>
<Accordion title="Одинаков ли тариф Organization в облаке и в самостоятельно размещаемой версии?">
Они предлагают одинаковые премиум-функции. Однако облачную подписку нельзя использовать для развертываний на собственном хостинге. Для самостоятельно размещаемой версии требуется ключ Enterprise.
</Accordion>
<Accordion title="Предлагаете ли вы бесплатные места для пользователей с правами только просмотра?">
@@ -32,7 +27,7 @@ description: Часто задаваемые вопросы о тарифах и
</Accordion>
<Accordion title="Где я могу переключить свою подписку на план Pro?">
Пожалуйста, свяжитесь с нашей командой напрямую через раздел «Поддержка», в данный момент нет простого способа сделать это через пользовательский интерфейс.
Пожалуйста, свяжитесь с нашей командой напрямую через Поддержку, в данный момент нет простого способа сделать это через пользовательский интерфейс.
</Accordion>
<Accordion title="Где я могу переключить свою подписку на годовой период?">
@@ -53,45 +53,38 @@ People ←→ Project Assignments ←→ Projects
1. Перейдите в **Настройки → Модель данных**
2. Нажмите **+ Новый объект**
3. Дайте ему понятное имя (например, "Project Assignment", "Team Member", "Product Order")
4. Включите переключатель «Пропустить создание поля „Имя“»
<img src="/images/user-guide/fields/new-pivot-object.png" alt="Новый объект-связка" />
5. Нажмите **Сохранить**
4. Нажмите **Сохранить**
<Tip>
**Рекомендации по именованию**: Используйте название, описывающее связь, например "Project Assignment" или "Team Membership". Так модель данных становится более понятной.
</Tip>
## Шаг 2: Создайте связи между объектами и объектом-связкой
## Шаг 2: Создайте связи из объекта-связки
Добавьте поля связи из каждого из двух объектов в объект-связку.
Добавьте поля связи из объекта-связки к обоим объектам, которые вы хотите соединить.
### Первая связь (Объект A → Объект-связка)
1. Выберите ваш первый объект в **Настройках → Модель данных**
2. Нажмите **+ Добавить связь**
3. Выберите объект-связку (например, "Назначения по проектам")
4. Установите тип связи **Один-ко-многим** (один человек может быть связан со многими назначениями)
5. Назовите поля:
* Поле в объекте Люди: например, "Project Assignments"
* Поле на объекте-связке: например, "Person"
6. Нажмите **Сохранить**
### Вторая связь (Объект B → Объект-связка)
1. Выберите ваш второй объект в **Настройках → Модель данных**
2. Нажмите **+ Добавить связь**
3. Выберите объект-связку (например, "Назначения по проектам")
4. Установите тип связи **Один-ко-многим** (один проект может быть связан со многими назначениями)
5. Включите **"Это связь с объектом-связкой"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### Первая связь (Объект-связка → Объект A)
1. Выберите ваш объект-связку в **Настройки → Модель данных**
2. Нажмите **+ Добавить поле**
3. Выберите тип поля **Связь**
4. Выберите первый объект (например, "Люди")
5. Установите тип связи **Многие-к-одному** (много назначений могут ссылаться на одного человека)
6. Назовите поля:
* Поле на объекте-связке: например, "Person"
* Поле в объекте Люди: например, "Project Assignments"
7. Нажмите **Сохранить**
### Вторая связь (Объект-связка → Объект B)
1. Оставаясь на объекте-связке, нажмите **+ Добавить поле**
2. Выберите тип поля **Связь**
3. Выберите второй объект (например, "Проекты")
4. Установите тип связи **Многие-к-одному**
5. Назовите поля:
* Поле на объекте-связке: например, "Project"
* Поле в объекте Проекты: например, "Team Members"
7. Нажмите **Сохранить**
6. Нажмите **Сохранить**
## Шаг 3: Настройте отображение связи через объект-связку
@@ -105,6 +98,18 @@ People ←→ Project Assignments ←→ Projects
6. Выберите **Целевую связь** (например, "Project" — поле на объекте-связке, указывающее на другую сторону)
7. Нажмите **Сохранить**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Повторите для другого объекта:
1. Выберите "Проекты" в Модели данных
2. Отредактируйте поле связи "Team Members"
3. Включите переключатель объекта-связки
4. Выберите "Person" как целевую связь
5. Сохранить
## Результат
После настройки:
@@ -125,15 +130,15 @@ People ←→ Project Assignments ←→ Projects
### Добавьте связи
1. **Люди → Назначение на проект**
* Тип: Один-ко-многим
* Поле в объекте Люди: "Project Assignments"
1. **Project Assignment → Люди**
* Тип: Многие-к-одному
* Поле в объекте Assignment: "Person"
* Поле в объекте Люди: "Project Assignments"
2. **Проекты → Назначение на проект**
* Тип: Один-ко-многим
* Поле в объекте Проекты: "Team Members"
2. **Project Assignment → Проекты**
* Тип: Многие-к-одному
* Поле в объекте Assignment: "Project"
* Поле в объекте Проекты: "Team Members"
### Настройте отображение объекта-связки
@@ -30,9 +30,3 @@ description: Настройте левую боковую панель под т
## Меню команд
Нажмите `Cmd+K` (или `Ctrl+K`), чтобы открыть меню команд — строку быстрого поиска для перехода к любой записи, представлению или действию, не используя боковую панель.
## Настройка боковой панели
Чтобы настроить боковую панель, наведите курсор на раздел "Workspace" на боковой панели и нажмите значок гаечного ключа.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Значок редактирования навигации" />
@@ -3,8 +3,6 @@ title: Страницы записей
description: Настройте макет отдельных страниц сведений о записях с вкладками и виджетами.
---
## Обзор
Когда вы открываете запись в Twenty, страница сведений состоит из **вкладок** и **виджетов**. Оба полностью настраиваются для каждого типа объекта.
## Вкладки
@@ -40,20 +38,12 @@ description: Настройте макет отдельных страниц с
1. Откройте любую запись
2. Нажмите `Cmd+K` и найдите "Изменить макет страницы записи"
или
1. Перейдите в Настройки > Модель данных > объект по вашему выбору > Макет
2. Нажмите кнопку "Настроить страницу записи" для этого объекта
3. Теперь вы в режиме настройки:
* **Добавляйте виджеты** из списка виджетов
* **Перетаскивайте виджеты**, чтобы изменить их положение на сетке
* **Изменяйте размер виджетов**, перетаскивая их границы
* **Настраивайте поля**, отображаемые в каждом виджете
* **Управляйте вкладками** — добавляйте, удаляйте, переименовывайте, меняйте порядок
4. Сохраните изменения — они применятся ко всем записям этого типа объекта
## Видимость полей
@@ -95,16 +95,15 @@ const handler = async (event: RoutePayload) => {
`RoutePayload` türünün yapısı şu şekildedir:
| Özellik | Tür | Açıklama | Örnek |
| ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | aşağıdaki bölüme bakın |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Rota deseninden çıkarılan yol parametreleri | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | JSON ayrıştırılmadan önceki özgün UTF-8 istek gövdesi. HMAC tarzı webhook imzalarını doğrulamak için kullanışlıdır (ör. GitHub'ın `X-Hub-Signature-256`, Stripe). Çalışma zamanı onu korumadığında `undefined` olur. | |
| `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | |
| `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Ham istek yolu | |
| Özellik | Tür | Açıklama | Örnek |
| ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | aşağıdaki bölüme bakın |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Rota deseninden çıkarılan yol parametreleri | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | |
| `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Ham istek yolu | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ Büyümeye hazır ekipler için:
* Standart destek
<Note>
Premium özellikler (SSO, satır düzeyi izinler ve Yapay Zeka kullanım verileri) Pro plana dahil değildir.
Premium özellikler (SSO ve satır düzeyi izinler) Pro planına dahil değildir.
</Note>
### Kuruluş (Bulut)
@@ -27,7 +27,7 @@ Premium özellikler (SSO, satır düzeyi izinler ve Yapay Zeka kullanım veriler
Gelişmiş ihtiyaçları olan daha büyük ekipler için:
* Pro'daki her şey
* **Premium özellikler**: SSO entegrasyonu, satır düzeyi izinler ve Yapay Zeka kullanım verileri
* **Premium özellikler**: SSO entegrasyonu ve satır düzeyi izinler
* Öncelikli destek
## Kendi Kendine Barındırılan Planlar
@@ -45,7 +45,7 @@ Twenty'yi kendi altyapınızda hiçbir ücret ödemeden barındırın:
Kendi kendine barındırma yaparken premium özelliklere ihtiyaç duyan ekipler için:
* Tüm Pro özellikleri
* **Premium özellikler**: SSO entegrasyonu, satır düzeyi izinler ve Yapay Zeka kullanım verileri
* **Premium özellikler**: SSO entegrasyonu ve satır düzeyi izinler
* Twenty ekibinden destek
* Dağıtmadan önce özel kodu açık kaynak olarak yayınlama zorunluluğu yoktur.
@@ -55,7 +55,6 @@ Premium özellikler yalnızca Kuruluş planlarında (Bulut veya Kendi Kendine Ba
* **SSO entegrasyonu**: Kimlik sağlayıcınızla Tek Oturum Açma
* **Satır düzeyi izinler**: Kayıt düzeyinde ayrıntılı erişim denetimi
* **Yapay Zeka kullanım verileri**: Çalışma alanı genelinde Yapay Zeka tüketimini takip edin
## Planlar Arasında Geçiş
@@ -78,15 +77,3 @@ Planınızı düşürmek için destek ekibiyle iletişime geçin.
### Aylığa Geçiş Yap
Aylık faturalamaya geri dönmek için destek ekibiyle iletişime geçin.
## Organizasyon (Kendi Sunucunuzda Barındırılan) için Kurumsal Anahtar edinin
Organizasyon (Kendi Sunucunuzda Barındırılan) planını kullanmak için bir Kurumsal Anahtar edinmeniz gerekir:
1. **Ayarlar → Yönetici Paneli → Kurumsal** bölümüne gidin
<img src="/images/user-guide/billing/enterprise-key.png" alt="Kurumsal Anahtar" />
2. **Kurumsal Anahtar Alın**'ı tıklayın
3. Stripe'a yönlendirildiğinizde ödeme bilgilerinizi girin ve onaylayın
4. Kurumsal anahtarınız görüntülendiğinde, bunu Kurumsal ayarlar sayfasına yapıştırın ve Organizasyon lisansını etkinleştirin
@@ -16,11 +16,6 @@ Kendiniz barındırmak istiyor ve Premium özelliklere (SSO ve satır düzeyi iz
Premium özellikler yalnızca Kuruluş planlarında (Bulut veya Kendi Barındırmalı) kullanılabilir:
* **SSO entegrasyonu**: Kimlik sağlayıcınızla Tek Oturum Açma
* **Satır düzeyi izinler**: Kayıt düzeyinde ayrıntılı erişim denetimi
* **Yapay Zeka kullanım verileri**: Çalışma alanı genelinde Yapay Zeka tüketimini takip edin
</Accordion>
<Accordion title="Buluttaki Organization planı ile kendi barındırmalı ortamdaki Organization planı aynı mı?">
İkisi de aynı Premium özellikleri sunar. Ancak bulut aboneliği, kendi barındırmalı dağıtımlarda kullanılamaz. Kendi barındırmalı kurulumlar için bir Enterprise anahtarı gerekir.
</Accordion>
<Accordion title="Salt görüntüleme kullanıcıları için ücretsiz koltuklar sunuyor musunuz?">
@@ -53,45 +53,38 @@ Bağlantı ilişkisi anahtarını etkinleştirdiğinizde, Twenty aradaki bağlan
1. **Ayarlar → Veri Modeli** bölümüne gidin
2. **+ Yeni nesne**'ye tıklayın
3. Açıklayıcı bir ad verin (örn. "Project Assignment", "Team Member", "Product Order")
4. "Ad alanı oluşturmayı atla" seçeneğini açın
<img src="/images/user-guide/fields/new-pivot-object.png" alt="Yeni pivot nesnesi" />
5. **Kaydet**'e tıklayın
4. **Kaydet**'e tıklayın
<Tip>
**Adlandırma kuralı**: "Project Assignment" veya "Team Membership" gibi ilişkiyi tanımlayan bir ad kullanın. Bu, veri modelinin anlaşılmasını kolaylaştırır.
</Tip>
## Adım 2: Nesneler ile Bağlantı nesnesi arasında ilişkiler oluşturun
## Adım 2: Bağlantı Nesnesinden İlişkiler Oluşturun
İki nesnenizin her birinden bağlantı nesnesine ilişki alanları ekleyin.
Bağlamak istediğiniz her iki nesneye de bağlantı nesnesinden ilişki alanları ekleyin.
### İlk İlişki (Nesne A → Bağlantı)
1. İlk nesnenizi **Ayarlar → Veri Modeli**'nde seçin
2. **+ İlişki Ekle**'ye tıklayın
3. Bağlantı nesnesini seçin (örn. "Project Assignments")
4. İlişki türünü **Birden-Çoğa** olarak ayarlayın (bir kişi birçok atamaya bağlanabilir)
5. Alanları adlandırın:
* People üzerindeki alan: örn. "Project Assignments"
* Bağlantı üzerindeki alan: örn. "Person"
6. **Kaydet**'e tıklayın
### İkinci İlişki (Nesne B → Bağlantı)
1. İkinci nesnenizi **Ayarlar → Veri Modeli**'nde seçin
2. **+ İlişki Ekle**'ye tıklayın
3. Bağlantı nesnesini seçin (örn. "Project Assignments")
4. İlişki türünü **Birden-Çoğa** olarak ayarlayın (bir proje birçok atamaya bağlanabilir)
5. **"Bu, bir bağlantı nesnesine kurulan bir ilişkidir"** seçeneğini etkinleştirin
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### İlk İlişki (Bağlantı → Nesne A)
1. **Ayarlar → Veri Modeli**'nde bağlantı nesnenizi seçin
2. **+ Alan Ekle**'ye tıklayın
3. Alan türü olarak **İlişki**'yi seçin
4. İlk nesneyi seçin (örn. "People")
5. İlişki türünü **Çoktan-Bire** olarak ayarlayın (birçok atama bir kişiye bağlanabilir)
6. Alanları adlandırın:
* Bağlantı üzerindeki alan: örn. "Person"
* People üzerindeki alan: örn. "Project Assignments"
7. **Kaydet**'e tıklayın
### İkinci İlişki (Bağlantı → Nesne B)
1. Hâlâ bağlantı nesnesindeyken, **+ Add Field**'e tıklayın
2. Alan türü olarak **İlişki**'yi seçin
3. İkinci nesneyi seçin (örn. "Projects")
4. İlişki türünü **Çoktan-Bire** olarak ayarlayın
5. Alanları adlandırın:
* Bağlantı üzerindeki alan: örn. "Project"
* Projects üzerindeki alan: örn. "Team Members"
7. **Kaydet**'e tıklayın
6. **Kaydet**'e tıklayın
## Adım 3: Bağlantı İlişkisi Görüntüsünü Yapılandırın
@@ -105,6 +98,18 @@ Bağlantı ilişkisi anahtarını etkinleştirdiğinizde, Twenty aradaki bağlan
6. **Hedef ilişki**yi seçin (örn. "Project" — bağlantı üzerindeki, diğer tarafı işaret eden alan)
7. **Kaydet**'e tıklayın
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Diğer nesne için tekrarlayın:
1. Veri Modeli'nde "Projects"i seçin
2. "Team Members" ilişki alanını düzenleyin
3. Bağlantı anahtarını etkinleştirin
4. Hedef ilişki olarak "Person"ı seçin
5. Kaydet
## Sonuç
Yapılandırmadan sonra:
@@ -125,15 +130,15 @@ Bağlantı nesnesi hâlâ mevcuttur ve bağlantıları saklar, ancak kullanıcı
### İlişkiler Ekleyin
1. **People → Project Assignment**
* Tür: Birden-Çoğa
* People üzerindeki alan: "Project Assignments"
1. **Project Assignment → People**
* Tür: Çoktan-Bire
* Assignment üzerindeki alan: "Person"
* People üzerindeki alan: "Project Assignments"
2. **Projects → Project Assignment**
* Tür: Birden-Çoğa
* Projects üzerindeki alan: "Team Members"
2. **Project Assignment → Projects**
* Tür: Çoktan-Bire
* Assignment üzerindeki alan: "Project"
* Projects üzerindeki alan: "Team Members"
### Bağlantı Görüntüsünü Yapılandırın
@@ -30,9 +30,3 @@ Harici araçlara yönelik bağlantıları doğrudan kenar çubuğuna ekleyin. Wi
## Komut Menüsü
Komut menüsünü açmak için `Cmd+K` (veya `Ctrl+K`) tuşlarına basın — kenar çubuğunda gezinmeden herhangi bir kayda, görünüme veya eyleme atlamak için hızlı erişim sunan bir arama çubuğu.
## Kenar Çubuğunu Özelleştirme
Kenar çubuğunu özelleştirmek için kenar çubuğundaki "Workspace" bölümünün üzerine gelin ve anahtar simgesine tıklayın.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Gezinti düzenleme simgesi" />
@@ -3,8 +3,6 @@ title: Kayıt Sayfaları
description: Her bir kayıt ayrıntı sayfasının düzenini sekmeler ve widget'larla özelleştirin.
---
## Genel Bakış
Twenty'de bir kaydı açtığınızda, ayrıntı sayfası **sekmeler** ve **widget'lar** içerir. Her ikisi de her nesne türü bazında tamamen özelleştirilebilir.
## Sekmeler
@@ -40,20 +38,12 @@ Widget'lar, her sekmenin içindeki yapı taşlarıdır. Kullanılabilir widget t
1. Herhangi bir kaydı açın
2. `Cmd+K` tuşlarına basın ve "Kayıt sayfası yerleşimini düzenle" ifadesini arayın
veya
1. Ayarlar > Veri modeli > seçtiğiniz nesne > Düzen'e gidin
2. O nesne için "Kayıt sayfasını özelleştir" düğmesine tıklayın
3. Artık özelleştirme modundasınız:
* **Widget ekleyin** widget seçicisinden
* **Widget'ları sürükleyin** ve ızgara üzerinde yeniden konumlandırın
* **Widget'ları yeniden boyutlandırın** kenarlarını sürükleyerek
* **Alanları yapılandırın** — her bir widget içinde gösterilen
* **Sekmeleri yönetin** — ekleyin, kaldırın, yeniden adlandırın, yeniden sıralayın
4. Değişikliklerinizi kaydedin — bu değişiklikler o nesne türündeki tüm kayıtlara uygulanır
## Alan görünürlüğü
@@ -95,16 +95,15 @@ const handler = async (event: RoutePayload) => {
`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' }` |
| `body` | `object \| null` | 已解析的请求体(JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | 在 JSON 解析之前的原始 UTF-8 请求体。 用于验证 HMAC 风格的 Webhook 签名(例如 GitHub 的 `X-Hub-Signature-256`、Stripe)。 当运行时未保留它时为 `undefined`。 | |
| `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | |
| `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE | |
| `requestContext.http.path` | `string` | 原始请求路径 | |
| 属性 | 类型 | 描述 | 示例 |
| ---------------------------- | ------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------- |
| `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' }` |
| `body` | `object \| null` | 已解析的请求体(JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | |
| `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE | |
| `requestContext.http.path` | `string` | 原始请求路径 | |
#### forwardedRequestHeaders
@@ -19,7 +19,7 @@ description: 了解 Twenty 的定价方案以及如何在它们之间切换。
* 标准支持
<Note>
Pro 方案不包含高级功能(SSO行级权限和 AI 使用数据)。
Pro 计划不包含高级功能(SSO行级权限)。
</Note>
### 组织计划(云端)
@@ -27,7 +27,7 @@ Pro 方案不包含高级功能(SSO、行级权限和 AI 使用数据)。
适用于具有高级需求的大型团队:
* 包含 Pro 的全部内容
* **高级功能**SSO 集成行级权限和 AI 使用数据
* **高级功能**SSO 集成行级权限
* 优先支持
## 自托管方案
@@ -45,7 +45,7 @@ Pro 方案不包含高级功能(SSO、行级权限和 AI 使用数据)。
适用于在自托管同时需要高级功能的团队:
* 所有 Pro 功能
* **高级功能**SSO 集成行级权限和 AI 使用数据
* **高级功能**SSO 集成行级权限
* Twenty 团队支持
* 分发前无需将自定义代码开源
@@ -55,7 +55,6 @@ Pro 方案不包含高级功能(SSO、行级权限和 AI 使用数据)。
* **SSO 集成**:与您的身份提供商进行单点登录
* **行级权限**:在记录级别进行细粒度访问控制
* **AI 使用数据**:在整个工作区跟踪 AI 消耗
## 切换计划
@@ -78,15 +77,3 @@ Pro 方案不包含高级功能(SSO、行级权限和 AI 使用数据)。
### 切换为按月计费
联系支持以切换回按月计费。
## 为 Organization (Self-Hosted) 获取企业密钥
要使用 Organization (Self-Hosted) 方案,您需要获取企业密钥:
1. 转到 **设置 → 管理员面板 → 企业版**
<img src="/images/user-guide/billing/enterprise-key.png" alt="企业密钥" />
2. 单击 **获取企业密钥**
3. 当您被重定向到 Stripe 时,输入您的付款信息并确认
4. 当显示出您的企业密钥时,将其粘贴到企业版设置页面并激活 Organization 许可证
@@ -16,11 +16,6 @@ description: 关于 Twenty 定价和账单的常见问题。
高级功能仅适用于 Organization 计划(云端或自托管):
* **SSO 集成**:与您的身份提供商进行单点登录
* **行级权限**:在记录级别进行细粒度的访问控制
* **AI 使用数据**:在整个工作区跟踪 AI 消耗
</Accordion>
<Accordion title="云端的 Organization 计划与自托管的 Organization 计划是否相同?">
它们提供相同的高级功能。 但是,云端订阅不能用于自托管部署。 自托管需要 Enterprise 密钥。
</Accordion>
<Accordion title="你们是否为只读用户提供免费席位?">
@@ -53,45 +53,38 @@ People ←→ Project Assignments ←→ Projects
1. 进入 **设置 → 数据模型**
2. 点击 **+ 新建对象**
3. 使用描述性名称(例如,"Project Assignment"、"Team Member"、"Product Order"
4. 将“跳过创建名称字段”切换为开启
<img src="/images/user-guide/fields/new-pivot-object.png" alt="新建连接对象" />
5. 单击 **保存**
4. 单击 **保存**
<Tip>
**命名约定**:使用能描述关系的名称,例如 "Project Assignment" 或 "Team Membership"。 这会让数据模型更易理解。
</Tip>
## 第 2 步:在对象与连接对象之间创建关系
## 步骤 2:从连接对象创建关系
您的两个对象分别向连接对象添加关系字段。
从连接对象向您要连接的两个对象添加关系字段。
### 第一条关系(对象 A → 连接对象)
1. 在 **Settings → Data Model** 中选择您的第一个对象
2. 单击 **+ 添加关系**
3. 选择连接对象(例如,"Project Assignments"
4. 将关系类型设置为 **One-To-Many**(一个人可以关联到多个分配)
5. 为这些字段命名:
* People 上的字段:例如,"Project Assignments"
* 连接对象上的字段:例如,"Person"
6. 单击 **保存**
### 第二条关系(对象 B → 连接对象)
1. 在 **Settings → Data Model** 中选择您的第二个对象
2. 单击 **+ 添加关系**
3. 选择连接对象(例如,"Project Assignments"
4. 将关系类型设置为 **One-To-Many**(一个项目可以关联到多个分配)
5. 启用 **"This is a relation to a Junction Object"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}} />
### 第一条关系(连接对象 → 对象 A
1. 在 **Settings → Data Model** 中选择您的连接对象
2. 点击 **+ 添加字段**
3. 将字段类型选择为 **Relation**
4. 选择第一个对象(例如,"People"
5. 将关系类型设置为 **Many-to-One**(多个分配可关联到一个人)
6. 为这些字段命名:
* 连接对象上的字段:例如,"Person"
* People 上的字段:例如,"Project Assignments"
7. 单击 **保存**
### 第二条关系(连接对象 → 对象 B)
1. 仍在连接对象中,点击 **+ Add Field**
2. 将字段类型选择为 **Relation**
3. 选择第二个对象(例如,"Projects"
4. 将关系类型设置为 **Many-to-One**
5. 为这些字段命名:
* 连接对象上的字段:例如,"Project"
* Projects 上的字段:例如,"Team Members"
7. 单击 **保存**
6. 单击 **保存**
## 步骤 3:配置连接关系的显示方式
@@ -105,6 +98,18 @@ People ←→ Project Assignments ←→ Projects
6. 选择 **Target relation**(例如,"Project" —— 连接对象上指向另一侧的字段)
7. 单击 **保存**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
对另一侧对象重复上述操作:
1. 在 Data Model 中选择 "Projects"
2. 编辑 "Team Members" 关系字段
3. 启用连接关系开关
4. 将目标关系选择为 "Person"
5. 保存
## 结果
配置完成后:
@@ -125,15 +130,15 @@ People ←→ Project Assignments ←→ Projects
### 添加关系
1. **People → Project Assignment**
* 类型:One-to-Many
* People 上的字段:"Project Assignments"
1. **Project Assignment → People**
* 类型:Many-to-One
* Assignment 上的字段:"Person"
* People 上的字段:"Project Assignments"
2. **Projects → Project Assignment**
* 类型:One-to-Many
* Projects 上的字段:"Team Members"
2. **Project Assignment → Projects**
* 类型:Many-to-One
* Assignment 上的字段:"Project"
* Projects 上的字段:"Team Members"
### 配置连接关系显示
@@ -30,9 +30,3 @@ description: 自定义左侧边栏,使其符合您团队的工作方式。
## 命令菜单
按下`Cmd+K`(或`Ctrl+K`)打开命令菜单——一个快速访问的搜索栏,无需通过侧边栏即可跳转到任何记录、视图或操作。
## 自定义侧边栏
要自定义侧边栏,请将鼠标悬停在侧边栏中的"工作区"部分,然后单击扳手图标。
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="导航编辑图标" />
@@ -3,8 +3,6 @@ title: 记录页面
description: 使用选项卡和小部件自定义单个记录详情页的布局。
---
## 概览
当你在 Twenty 中打开一条记录时,详情页由**选项卡**和**小部件**组成。 两者都可按对象类型完全自定义。
## 选项卡
@@ -40,20 +38,12 @@ description: 使用选项卡和小部件自定义单个记录详情页的布局
1. 打开任意记录
2. 按下 `Cmd+K`,搜索“编辑记录页面布局”
1. 前往 设置 > 数据模型 > 所选对象 > 布局
2. 单击该对象的“自定义记录页面”按钮
3. 你现在处于自定义模式:
* 从小部件选择器中**添加小部件**
* **拖动小部件**,在网格上重新定位它们
* **调整小部件大小**,通过拖动其边缘
* **配置字段**,设置每个小部件内显示的字段
* **管理选项卡** — 添加、删除、重命名、重新排序
4. 保存你的更改—它们将应用于该对象类型的所有记录
## 字段可见性
@@ -17,13 +17,13 @@ For teams ready to scale:
- Standard support
<Note>
Premium features (SSO, row-level permissions and AI usage data) are not included in the Pro plan.
Premium features (SSO and row-level permissions) are not included in the Pro plan.
</Note>
### Organization (Cloud)
For larger teams with advanced needs:
- Everything in Pro
- **Premium features**: SSO integration, row-level permissions and AI usage data
- **Premium features**: SSO integration and row-level permissions
- Priority support
## Self-Hosted Plans
@@ -37,7 +37,7 @@ Host Twenty on your own infrastructure at no cost:
### Organization (Self-Hosted)
For teams who need premium features while self-hosting:
- All Pro features
- **Premium features**: SSO integration, row-level permissions and AI usage data
- **Premium features**: SSO integration and row-level permissions
- Twenty team support
- No requirement to publish custom code as open-source before distributing
@@ -46,7 +46,6 @@ For teams who need premium features while self-hosting:
Premium features are only available on the Organization plans (Cloud or Self-Hosted):
- **SSO integration**: Single Sign-On with your identity provider
- **Row-level permissions**: Fine-grained access control at the record level
- **AI usage data**: Track AI consumption across the workspace
## Switching Plans
@@ -66,15 +65,3 @@ Contact support to downgrade your plan.
### Switch to Monthly Billing
Contact support to switch back to monthly billing.
## Obtain an Enterprise Key for Organization (Self-Hosted)
To use the Organization (Self-Hosted) plan, you need to obtain an Enterprise key:
1. Go to **Settings → Admin Panel → Enterprise**
<img src="/images/user-guide/billing/enterprise-key.png" alt="Enterprise key" />
2. Click **Get Enterprise Key**
3. When you are redirected to Stripe, enter your payment details and confirm
4. When your Enterprise key is displayed, paste it into the Enterprise settings page and activate the Organization license
@@ -16,11 +16,6 @@ If you want to self-host and need the Premium features (SSO and row-level permis
Premium features are only available on the Organization plans (Cloud or Self-Hosted):
- **SSO integration**: Single Sign-On with your identity provider
- **Row-level permissions**: Fine-grained access control at the record level
- **AI usage data**: Track AI consumption across the workspace
</Accordion>
<Accordion title="Is Organization plan on cloud and Organization plan on self-hosted the same?">
They offer the same premium features. However, a cloud subscription cannot be used for self-hosted deployments. Self-hosted requires an Enterprise key.
</Accordion>
<Accordion title="Do you offer free seats for view-only users?">
@@ -52,45 +52,38 @@ First, create the intermediate object that will hold the connections.
1. Go to **Settings → Data Model**
2. Click **+ New object**
3. Name it descriptively (e.g., "Project Assignment", "Team Member", "Product Order")
4. Toggle "Skip creating a Name field" on
<img src="/images/user-guide/fields/new-pivot-object.png" alt="New pivot object" />
5. Click **Save**
4. Click **Save**
<Tip>
**Naming convention**: Use a name that describes the relationship, like "Project Assignment" or "Team Membership". This makes the data model easier to understand.
</Tip>
## Step 2: Create Relations Between Objects and the Junction
## Step 2: Create Relations from the Junction Object
Add relation fields from each of your two objects to the junction object.
Add relation fields from the junction object to both objects you want to connect.
### First Relation (Object A → Junction)
1. Select your first object in **Settings → Data Model**
2. Click **+ Add Relation**
3. Select the junction object (e.g., "Project Assignments")
4. Set the relation type to **One-To-Many** (one person can link to many assignments)
5. Name the fields:
- Field on People: e.g., "Project Assignments"
- Field on junction: e.g., "Person"
6. Click **Save**
### Second Relation (Object B → Junction)
1. Select your second object in **Settings → Data Model**
2. Click **+ Add Relation**
3. Select the junction object (e.g., "Project Assignments")
4. Set the relation type to **One-To-Many** (one project can link to many assignments)
5. Enable **"This is a relation to a Junction Object"**
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
### First Relation (Junction → Object A)
1. Select your junction object in **Settings → Data Model**
2. Click **+ Add Field**
3. Choose **Relation** as the field type
4. Select the first object (e.g., "People")
5. Set the relation type to **Many-to-One** (many assignments can link to one person)
6. Name the fields:
- Field on junction: e.g., "Person"
- Field on People: e.g., "Project Assignments"
7. Click **Save**
### Second Relation (Junction → Object B)
1. Still on the junction object, click **+ Add Field**
2. Choose **Relation** as the field type
3. Select the second object (e.g., "Projects")
4. Set the relation type to **Many-to-One**
5. Name the fields:
- Field on junction: e.g., "Project"
- Field on Projects: e.g., "Team Members"
7. Click **Save**
6. Click **Save**
## Step 3: Configure the Junction Relation Display
@@ -104,6 +97,18 @@ Now configure the source objects to display linked records directly, skipping th
6. Select the **Target relation** (e.g., "Project" — the field on the junction that points to the other side)
7. Click **Save**
{/* TODO: Add image
<img src="/images/user-guide/fields/junction-relation-toggle.png" style={{width:'100%'}}/>
*/}
Repeat for the other object:
1. Select "Projects" in Data Model
2. Edit the "Team Members" relation field
3. Enable the junction toggle
4. Select "Person" as the target relation
5. Save
## Result
After configuration:
@@ -122,15 +127,15 @@ Here's a complete walkthrough:
- Description: "Links people to projects they work on"
### Add Relations
1. **People → Project Assignment**
- Type: One-to-Many
- Field on People: "Project Assignments"
1. **Project Assignment → People**
- Type: Many-to-One
- Field on Assignment: "Person"
- Field on People: "Project Assignments"
2. **Projects → Project Assignment**
- Type: One-to-Many
- Field on Projects: "Team Members"
2. **Project Assignment → Projects**
- Type: Many-to-One
- Field on Assignment: "Project"
- Field on Projects: "Team Members"
### Configure Junction Display
1. On **People** object:
@@ -30,9 +30,3 @@ Add links to external tools directly in the sidebar. Useful for linking to your
## 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.
## Customizing the sidebar
To customize the sidebar, hover over the "Workspace" section in the sidebar and click the wrench icon.
<img src="/images/user-guide/layout/navigation-edit-icon.png" alt="Navigation edit icon" />
@@ -3,8 +3,6 @@ title: Record Pages
description: Customize the layout of individual record detail pages with tabs and widgets.
---
## Overview
When you open a record in Twenty, the detail page is composed of **tabs** and **widgets**. Both are fully customizable per object type.
## Tabs
@@ -39,12 +37,6 @@ Widgets are the building blocks inside each tab. Available widget types include:
1. Open any record
2. Press `Cmd+K` and search for "Edit record page layout"
or
1. Go to Settings > Data model > object of your choice > Layout
2. Click the "Customize record page" button for that object
3. You're now in customization mode:
- **Add widgets** from the widget picker
- **Drag widgets** to reposition them on the grid
+19 -3
View File
@@ -1,6 +1,6 @@
import { type Messages } from '@lingui/core';
import { createI18nInstanceFactory } from 'twenty-shared/i18n';
import { setupI18n, type I18n, type Messages } from '@lingui/core';
import { type APP_LOCALES } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
import { messages as afMessages } from '@/locales/generated/af-ZA';
import { messages as arMessages } from '@/locales/generated/ar-SA';
import { messages as caMessages } from '@/locales/generated/ca-ES';
@@ -67,4 +67,20 @@ const messages: Record<keyof typeof APP_LOCALES, Messages> = {
'zh-TW': zhHantMessages,
};
export const createI18nInstance = createI18nInstanceFactory(messages);
const i18nInstancesMap: Partial<Record<keyof typeof APP_LOCALES, I18n>> = {};
export const createI18nInstance = (locale: keyof typeof APP_LOCALES): I18n => {
if (isDefined(i18nInstancesMap[locale])) {
return i18nInstancesMap[locale];
}
const i18nInstance = setupI18n();
const localeMessages = messages[locale] ?? messages.en;
i18nInstance.load(locale, localeMessages);
i18nInstance.activate(locale);
i18nInstancesMap[locale] = i18nInstance;
return i18nInstance;
};
@@ -10,10 +10,6 @@ export type SerializedEventData = {
pageY?: number;
screenX?: number;
screenY?: number;
offsetX?: number;
offsetY?: number;
movementX?: number;
movementY?: number;
button?: number;
buttons?: number;
key?: string;
@@ -81,12 +81,6 @@ const serializeEvent = (event: unknown): SerializedEventData => {
if ('pageY' in domEvent) serialized.pageY = domEvent.pageY as number;
if ('screenX' in domEvent) serialized.screenX = domEvent.screenX as number;
if ('screenY' in domEvent) serialized.screenY = domEvent.screenY as number;
if ('offsetX' in domEvent) serialized.offsetX = domEvent.offsetX as number;
if ('offsetY' in domEvent) serialized.offsetY = domEvent.offsetY as number;
if ('movementX' in domEvent)
serialized.movementX = domEvent.movementX as number;
if ('movementY' in domEvent)
serialized.movementY = domEvent.movementY as number;
if ('button' in domEvent) serialized.button = domEvent.button as number;
if ('buttons' in domEvent) serialized.buttons = domEvent.buttons as number;
+2 -2
View File
@@ -61,8 +61,8 @@ const jestConfig = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
coverageThreshold: {
global: {
statements: 47.3,
lines: 45.9,
statements: 47.9,
lines: 46,
functions: 39.5,
},
},
+2 -2
View File
@@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
"build": "NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 npx vite build",
"build:sourcemaps": "NODE_ENV=production VITE_BUILD_SOURCEMAP=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build",
"build": "NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
"build:sourcemaps": "NODE_ENV=production VITE_BUILD_SOURCEMAP=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
"start:prod": "NODE_ENV=production npx serve -s build",
"tsup": "npx tsup"
},
@@ -1,10 +1,5 @@
#!/bin/sh
if [ -z "$REACT_APP_SERVER_BASE_URL" ]; then
echo "Error: REACT_APP_SERVER_BASE_URL is not set."
exit 1
fi
echo "Injecting runtime environment variables into index.html..."
CONFIG_BLOCK=$(cat << EOF
+3 -1
View File
@@ -18,4 +18,6 @@ const getDefaultUrl = () => {
};
export const REACT_APP_SERVER_BASE_URL =
window._env_?.REACT_APP_SERVER_BASE_URL || getDefaultUrl();
window._env_?.REACT_APP_SERVER_BASE_URL ||
process.env.REACT_APP_SERVER_BASE_URL ||
getDefaultUrl();
@@ -83,21 +83,19 @@ export enum AdminPanelHealthServiceStatus {
export type AdminPanelRecentUser = {
__typename?: 'AdminPanelRecentUser';
avatarUrl?: Maybe<Scalars['String']>;
createdAt: Scalars['DateTime'];
email: Scalars['String'];
firstName?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
lastName?: Maybe<Scalars['String']>;
workspaceId?: Maybe<Scalars['UUID']>;
workspaceLogo?: Maybe<Scalars['String']>;
workspaceName?: Maybe<Scalars['String']>;
};
export type AdminPanelTopWorkspace = {
__typename?: 'AdminPanelTopWorkspace';
id: Scalars['UUID'];
logoUrl?: Maybe<Scalars['String']>;
logo?: Maybe<Scalars['String']>;
name: Scalars['String'];
subdomain: Scalars['String'];
totalUsers: Scalars['Int'];
@@ -668,7 +666,6 @@ export type UsageBreakdownItem = {
export type UserInfo = {
__typename?: 'UserInfo';
avatarUrl?: Maybe<Scalars['String']>;
createdAt: Scalars['DateTime'];
email: Scalars['String'];
firstName?: Maybe<Scalars['String']>;
@@ -832,7 +829,7 @@ export type GetModelsDevSuggestionsQuery = { __typename?: 'Query', getModelsDevS
export type FindAllApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type CreateDatabaseConfigVariableMutationVariables = Exact<{
key: Scalars['String'];
@@ -885,21 +882,21 @@ export type AdminPanelRecentUsersQueryVariables = Exact<{
}>;
export type AdminPanelRecentUsersQuery = { __typename?: 'Query', adminPanelRecentUsers: Array<{ __typename?: 'AdminPanelRecentUser', id: string, email: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, createdAt: string, workspaceName?: string | null, workspaceId?: string | null, workspaceLogo?: string | null }> };
export type AdminPanelRecentUsersQuery = { __typename?: 'Query', adminPanelRecentUsers: Array<{ __typename?: 'AdminPanelRecentUser', id: string, email: string, firstName?: string | null, lastName?: string | null, createdAt: string, workspaceName?: string | null, workspaceId?: string | null }> };
export type AdminPanelTopWorkspacesQueryVariables = Exact<{
searchTerm?: InputMaybe<Scalars['String']>;
}>;
export type AdminPanelTopWorkspacesQuery = { __typename?: 'Query', adminPanelTopWorkspaces: Array<{ __typename?: 'AdminPanelTopWorkspace', id: string, logoUrl?: string | null, name: string, totalUsers: number, subdomain: string }> };
export type AdminPanelTopWorkspacesQuery = { __typename?: 'Query', adminPanelTopWorkspaces: Array<{ __typename?: 'AdminPanelTopWorkspace', id: string, name: string, totalUsers: number, subdomain: string, logo?: string | null }> };
export type FindOneAdminApplicationRegistrationQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type FindOneAdminApplicationRegistrationQuery = { __typename?: 'Query', findOneAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type FindOneAdminApplicationRegistrationQuery = { __typename?: 'Query', findOneAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type GetAdminChatThreadMessagesQueryVariables = Exact<{
threadId: Scalars['UUID'];
@@ -939,7 +936,7 @@ export type WorkspaceLookupAdminPanelQueryVariables = Exact<{
}>;
export type WorkspaceLookupAdminPanelQuery = { __typename?: 'Query', workspaceLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null, createdAt: string }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, allowImpersonation: boolean, logo?: string | null, totalUsers: number, activationStatus: WorkspaceActivationStatus, createdAt: string, workspaceUrls: { __typename?: 'WorkspaceUrls', customUrl?: string | null, subdomainUrl: string }, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } };
export type WorkspaceLookupAdminPanelQuery = { __typename?: 'Query', workspaceLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null, createdAt: string }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, allowImpersonation: boolean, logo?: string | null, totalUsers: number, activationStatus: WorkspaceActivationStatus, createdAt: string, workspaceUrls: { __typename?: 'WorkspaceUrls', customUrl?: string | null, subdomainUrl: string }, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } };
export type DeleteJobsMutationVariables = Exact<{
queueName: Scalars['String'];
@@ -1006,10 +1003,10 @@ export type GetMaintenanceModeQueryVariables = Exact<{ [key: string]: never; }>;
export type GetMaintenanceModeQuery = { __typename?: 'Query', getMaintenanceMode?: { __typename?: 'MaintenanceMode', startAt: string, endAt: string, link?: string | null } | null };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export const UserInfoFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<UserInfoFragmentFragment, unknown>;
export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ApplicationRegistrationFragmentFragment, unknown>;
export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<ApplicationRegistrationFragmentFragment, unknown>;
export const AddAiProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddAiProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerConfig"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addAiProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}},{"kind":"Argument","name":{"kind":"Name","value":"providerConfig"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerConfig"}}}]}]}}]} as unknown as DocumentNode<AddAiProviderMutation, AddAiProviderMutationVariables>;
export const AddModelToProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddModelToProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelConfig"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addModelToProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelConfig"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelConfig"}}}]}]}}]} as unknown as DocumentNode<AddModelToProviderMutation, AddModelToProviderMutationVariables>;
export const RemoveAiProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveAiProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeAiProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}}]}]}}]} as unknown as DocumentNode<RemoveAiProviderMutation, RemoveAiProviderMutationVariables>;
@@ -1024,22 +1021,22 @@ export const GetAdminAiUsageByWorkspaceDocument = {"kind":"Document","definition
export const GetAiProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAiProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAiProviders"}}]}}]} as unknown as DocumentNode<GetAiProvidersQuery, GetAiProvidersQueryVariables>;
export const GetModelsDevProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"modelCount"}},{"kind":"Field","name":{"kind":"Name","value":"npm"}}]}}]}}]} as unknown as DocumentNode<GetModelsDevProvidersQuery, GetModelsDevProvidersQueryVariables>;
export const GetModelsDevSuggestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevSuggestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevSuggestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"outputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cachedInputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cacheCreationCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"contextWindowTokens"}},{"kind":"Field","name":{"kind":"Name","value":"maxOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"modalities"}},{"kind":"Field","name":{"kind":"Name","value":"supportsReasoning"}}]}}]}}]} as unknown as DocumentNode<GetModelsDevSuggestionsQuery, GetModelsDevSuggestionsQueryVariables>;
export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
export const CreateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<CreateDatabaseConfigVariableMutation, CreateDatabaseConfigVariableMutationVariables>;
export const DeleteDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}]}]}}]} as unknown as DocumentNode<DeleteDatabaseConfigVariableMutation, DeleteDatabaseConfigVariableMutationVariables>;
export const UpdateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<UpdateDatabaseConfigVariableMutation, UpdateDatabaseConfigVariableMutationVariables>;
export const GetConfigVariablesGroupedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConfigVariablesGrouped"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConfigVariablesGrouped"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"groups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isHiddenOnLoad"}},{"kind":"Field","name":{"kind":"Name","value":"variables"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"isSensitive"}},{"kind":"Field","name":{"kind":"Name","value":"isEnvOnly"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}}]}}]}}]} as unknown as DocumentNode<GetConfigVariablesGroupedQuery, GetConfigVariablesGroupedQueryVariables>;
export const GetDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"isSensitive"}},{"kind":"Field","name":{"kind":"Name","value":"isEnvOnly"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"source"}}]}}]}}]} as unknown as DocumentNode<GetDatabaseConfigVariableQuery, GetDatabaseConfigVariableQueryVariables>;
export const UpdateWorkspaceFeatureFlagDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceFeatureFlag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspaceFeatureFlag"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"featureFlag"},"value":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>;
export const AdminPanelRecentUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelRecentUsers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelRecentUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceLogo"}}]}}]}}]} as unknown as DocumentNode<AdminPanelRecentUsersQuery, AdminPanelRecentUsersQueryVariables>;
export const AdminPanelTopWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelTopWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelTopWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"subdomain"}}]}}]}}]} as unknown as DocumentNode<AdminPanelTopWorkspacesQuery, AdminPanelTopWorkspacesQueryVariables>;
export const FindOneAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneAdminApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneAdminApplicationRegistrationQuery, FindOneAdminApplicationRegistrationQueryVariables>;
export const AdminPanelRecentUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelRecentUsers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelRecentUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}}]}}]}}]} as unknown as DocumentNode<AdminPanelRecentUsersQuery, AdminPanelRecentUsersQueryVariables>;
export const AdminPanelTopWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelTopWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelTopWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"subdomain"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}}]}}]}}]} as unknown as DocumentNode<AdminPanelTopWorkspacesQuery, AdminPanelTopWorkspacesQueryVariables>;
export const FindOneAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneAdminApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<FindOneAdminApplicationRegistrationQuery, FindOneAdminApplicationRegistrationQueryVariables>;
export const GetAdminChatThreadMessagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminChatThreadMessages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminChatThreadMessages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"messages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"parts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"toolName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<GetAdminChatThreadMessagesQuery, GetAdminChatThreadMessagesQueryVariables>;
export const GetAdminWorkspaceChatThreadsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminWorkspaceChatThreads"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminWorkspaceChatThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<GetAdminWorkspaceChatThreadsQuery, GetAdminWorkspaceChatThreadsQueryVariables>;
export const GetVersionInfoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetVersionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"currentVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}}]}}]}}]} as unknown as DocumentNode<GetVersionInfoQuery, GetVersionInfoQueryVariables>;
export const WorkspaceBillingAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceBillingAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBillingAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripeCustomerId"}},{"kind":"Field","name":{"kind":"Name","value":"creditBalance"}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripeSubscriptionId"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"interval"}},{"kind":"Field","name":{"kind":"Name","value":"currency"}},{"kind":"Field","name":{"kind":"Name","value":"planKey"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodStart"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"trialStart"}},{"kind":"Field","name":{"kind":"Name","value":"trialEnd"}},{"kind":"Field","name":{"kind":"Name","value":"cancelAt"}},{"kind":"Field","name":{"kind":"Name","value":"canceledAt"}},{"kind":"Field","name":{"kind":"Name","value":"cancelAtPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"productKey"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"quantity"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"includedCredits"}}]}}]}}]}}]}}]} as unknown as DocumentNode<WorkspaceBillingAdminPanelQuery, WorkspaceBillingAdminPanelQueryVariables>;
export const UserLookupAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserLookupAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"userIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userLookupAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"userIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"userIdentifier"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserInfoFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"activationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"allowImpersonation"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customUrl"}},{"kind":"Field","name":{"kind":"Name","value":"subdomainUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featureFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<UserLookupAdminPanelQuery, UserLookupAdminPanelQueryVariables>;
export const WorkspaceLookupAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceLookupAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceLookupAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserInfoFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"allowImpersonation"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"activationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customUrl"}},{"kind":"Field","name":{"kind":"Name","value":"subdomainUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featureFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<WorkspaceLookupAdminPanelQuery, WorkspaceLookupAdminPanelQueryVariables>;
export const WorkspaceLookupAdminPanelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceLookupAdminPanel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceLookupAdminPanel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserInfoFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"allowImpersonation"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"activationStatus"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customUrl"}},{"kind":"Field","name":{"kind":"Name","value":"subdomainUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"users"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"featureFlags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode<WorkspaceLookupAdminPanelQuery, WorkspaceLookupAdminPanelQueryVariables>;
export const DeleteJobsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteJobs"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteJobs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"queueName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}}},{"kind":"Argument","name":{"kind":"Name","value":"jobIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletedCount"}},{"kind":"Field","name":{"kind":"Name","value":"results"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"jobId"}},{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]}}]} as unknown as DocumentNode<DeleteJobsMutation, DeleteJobsMutationVariables>;
export const RetryJobsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RetryJobs"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"retryJobs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"queueName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"queueName"}}},{"kind":"Argument","name":{"kind":"Name","value":"jobIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"jobIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"retriedCount"}},{"kind":"Field","name":{"kind":"Name","value":"results"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"jobId"}},{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]}}]} as unknown as DocumentNode<RetryJobsMutation, RetryJobsMutationVariables>;
export const GetIndicatorHealthStatusDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetIndicatorHealthStatus"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"indicatorId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"HealthIndicatorId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getIndicatorHealthStatus"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"indicatorId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"indicatorId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}},{"kind":"Field","name":{"kind":"Name","value":"details"}},{"kind":"Field","name":{"kind":"Name","value":"queues"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"queueName"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]}}]} as unknown as DocumentNode<GetIndicatorHealthStatusQuery, GetIndicatorHealthStatusQueryVariables>;
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More