Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code ee2f8e0226 fix: handle missing protocol in origin for getSubdomainAndDomainFromUrl
https://sonarly.com/issue/4546?type=bug

The `GetPublicWorkspaceDataByDomain` query crashes with `TypeError: Invalid URL` when the `origin` variable is a bare hostname without a protocol scheme (e.g., `hslmx.twenty.com` instead of `https://hslmx.twenty.com`).

Fix: Added URL protocol normalization in `DomainServerConfigService.getSubdomainAndDomainFromUrl()`. When the input URL string lacks a protocol scheme (e.g., `hslmx.twenty.com` instead of `https://hslmx.twenty.com`), the method now prepends `https://` before passing it to `new URL()`. This prevents the `TypeError: Invalid URL` that occurs when API clients call `GetPublicWorkspaceDataByDomain` with a bare hostname.

The check `url.includes('://')` is used rather than a regex to stay consistent with the codebase's simple string operations. Full URLs (from browser `window.location.origin`) pass through unchanged.

Added 3 test cases to the existing test file:
1. Extracts subdomain from a full URL with protocol (existing behavior preserved)
2. Handles URL without protocol scheme (the bug fix)
3. Returns custom domain for non-front domain URLs
2026-03-27 20:21:58 +00:00
Charles BochetandGitHub cb44b22e15 Fix INDEX view showing labelPlural instead of resolved view name in nav (#19049)
## Summary
- Navigation sidebar was displaying "Notes" instead of "All Notes" for
INDEX views
- `getNavigationMenuItemLabel` had a special case for INDEX views that
returned `objectMetadataItem.labelPlural` instead of the
already-resolved `view.name`
- Since `viewsSelector` already resolves `{objectLabelPlural}` templates
via `resolveViewNamePlaceholders`, the INDEX special case was redundant
and incorrect — removed it from both `getNavigationMenuItemLabel` and
`getViewNavigationMenuItemLabel`
- Added unit tests covering all navigation menu item type branches

<img width="222" height="479" alt="image"
src="https://github.com/user-attachments/assets/de6f92b1-e0c2-445a-a145-cf2820434af7"
/>
2026-03-27 17:25:52 +00:00
9498a60f74 i18n - translations (#19048)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-27 17:39:22 +01:00
Charles BochetandGitHub 191a277ddf fix: invalidate rolesPermissions cache + add Docker Hub auth to CI (#19044)
## Summary

### Cache invalidation fix
- After migrating object/field permissions to syncable entities (#18609,
#18751, #18567), changes to `flatObjectPermissionMaps`,
`flatFieldPermissionMaps`, or `flatPermissionFlagMaps` no longer
triggered `rolesPermissions` cache invalidation
- This caused stale permission data to be served, leading to flaky
`permissions-on-relations` integration tests and potentially incorrect
permission enforcement in production after object permission upserts
- Adds the three permission-related flat map keys to the condition that
triggers `rolesPermissions` cache recomputation in
`WorkspaceMigrationRunnerService.getLegacyCacheInvalidationPromises`
- Clears memoizer after recomputation to prevent concurrent
`getOrRecompute` calls from caching stale data

### Docker Hub rate limit fix
- CI service containers (postgres, redis, clickhouse) and `docker
run`/`docker build` steps were pulling from Docker Hub
**unauthenticated**, hitting the 100-pull-per-6-hour rate limit on
shared GitHub-hosted runner IPs
- Adds `credentials` blocks to all service container definitions and
`docker/login-action` steps before `docker run`/`docker compose`
commands
- Uses `vars.DOCKERHUB_USERNAME` + `secrets.DOCKERHUB_PASSWORD`
(matching the existing twenty-infra convention)
- Affected workflows: ci-server, ci-merge-queue, ci-breaking-changes,
ci-zapier, ci-sdk, ci-create-app-e2e, ci-website,
ci-test-docker-compose, preview-env-keepalive, spawn-twenty-docker-image
action
2026-03-27 17:32:53 +01:00
7f1814805d Fix backfill record page layout command (#19043)
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-27 16:56:30 +01:00
34b81adce8 i18n - translations (#19046)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-27 16:38:08 +01:00
Félix MalfaitandGitHub cd2e08b912 Enforce workspace count limit for multi-workspace setups (#19036)
## Summary
- Limits workspace creation to 5 workspaces per server when no valid
enterprise key is configured
- Enterprise key validity is checked first (synchronous, in-memory
cached) to avoid unnecessary DB queries on enterprise deployments
- Adds 4 unit tests covering: limit enforcement, enterprise key bypass,
below-limit creation, and performance (no enterprise check when
workspace count is zero)

## Test plan
- [x] All 11 unit tests pass (8 existing + 3 new behavioral + 1
performance assertion)
- [ ] Manual: verify workspace creation blocked at limit=5 without
enterprise key
- [ ] Manual: verify workspace creation allowed beyond limit with valid
enterprise key
- [ ] Manual: verify first workspace (bootstrap) creation is unaffected


Made with [Cursor](https://cursor.com)
2026-03-27 16:31:27 +01:00
Charles BochetandGitHub fb21f3ccf5 fix: resolve availabilityObjectMetadataUniversalIdentifier in backfill command (#19041)
## Summary

- Fixes workflow manual trigger command menu items losing their
`availabilityObjectMetadataId` during the 1-20 backfill upgrade command
- The migration runner's `transpileUniversalActionToFlatAction` resolves
`availabilityObjectMetadataId` **from**
`availabilityObjectMetadataUniversalIdentifier`, overwriting the
original value. The backfill was hardcoding the universal identifier to
`null`, causing all `RECORD_SELECTION` items to end up with `NULL`
`availabilityObjectMetadataId` after persistence.
- Now properly resolves `availabilityObjectMetadataUniversalIdentifier`
from `flatObjectMetadataMaps.universalIdentifierById` so the runner can
correctly derive the FK during INSERT.
2026-03-27 16:01:31 +01:00
Paul RastoinandGitHub 281bb6d783 Guard yarn database:migrate:prod (#19008)
## Motivations
A lot of self hosters hands up using the `yarn database:migrated:prod`
either manually or through AI assisted debug while they try to upgrade
an instance while their workspace is still blocked in a previous one
Leading to their whole database permanent corruption

## What happened
Replaced the direct call the the typeorm cli to a command calling it
programmatically, adding a layer of security in case a workspace seems
to be blocked in a previous version than the one just before the one
being installed ( e.g 1.0 when you try to upgrade from 1.1 to 1.2 )

For our cloud we still need a way to bypass this security explaining the
-f flag

## Remark
Centralized this logic and refactored creating new services
`WorkspaceVersionService` and `CoreEngineVersionService` that will
become useful for the upcoming upgrade refactor

Related to https://github.com/twentyhq/twenty-infra/pull/529
2026-03-27 14:39:18 +00:00
128 changed files with 2457 additions and 1571 deletions
+15 -2
View File
@@ -36,6 +36,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -50,10 +53,16 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
@@ -143,7 +152,6 @@ jobs:
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Seed current branch database with test data
run: |
@@ -296,7 +304,6 @@ jobs:
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Seed main branch database with test data
run: |
@@ -395,6 +402,12 @@ jobs:
# Clean up temp directory
rm -rf /tmp/current-branch-files
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Install OpenAPI Diff Tool
run: |
# Using the Java-based OpenAPITools/openapi-diff via Docker
+6
View File
@@ -37,6 +37,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -51,6 +54,9 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
env:
+6
View File
@@ -26,6 +26,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -40,6 +43,9 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
+6
View File
@@ -57,6 +57,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -71,6 +74,9 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
env:
+15 -1
View File
@@ -84,6 +84,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -98,6 +101,9 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
@@ -122,7 +128,6 @@ jobs:
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Worker / Run
run: |
timeout 30s npx nx run twenty-server:worker || exit_code=$?
@@ -227,6 +232,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -241,10 +249,16 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
@@ -27,6 +27,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run compose
run: |
echo "Patching docker-compose.yml..."
@@ -97,6 +102,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Create frontend placeholder
run: |
mkdir -p packages/twenty-front/build
+3
View File
@@ -27,6 +27,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
+6
View File
@@ -30,6 +30,9 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -44,6 +47,9 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
@@ -17,6 +17,12 @@ jobs:
with:
ref: ${{ github.event.client_payload.pr_head_sha }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run compose setup
run: |
echo "Patching docker-compose.yml..."
@@ -32,11 +32,11 @@ has_schema=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
if [ "$has_schema" = "f" ]; then
echo "Database appears to be empty, running initial setup..."
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/scripts/setup-db.js
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/database/scripts/setup-db.js
fi
# Always run migrations (idempotent — skips already-applied ones)
yarn database:migrate:prod
yarn database:migrate:prod --force
yarn command:prod cache:flush
yarn command:prod upgrade
-6
View File
@@ -38,12 +38,6 @@ RUN npx nx run twenty-server:lingui:extract && \
RUN npx nx run twenty-server:build
# Bundle setup-db script into a standalone JS file so the final image
# doesn't need tsx or the TypeScript source tree at runtime.
RUN npx esbuild packages/twenty-server/scripts/setup-db.ts \
--bundle --platform=node --outfile=packages/twenty-server/dist/scripts/setup-db.js \
--external:typeorm --external:dotenv --external:pg
# Clean server build output (type declarations and compiled tests are not needed at runtime;
# source maps are kept because twenty-infra extracts them from the image for Sentry uploads)
RUN find /app/packages/twenty-server/dist -name '*.d.ts' -delete \
+1 -2
View File
@@ -13,8 +13,7 @@ setup_and_migrate_db() {
has_schema=$(psql -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')" ${PG_DATABASE_URL})
if [ "$has_schema" = "f" ]; then
echo "Database appears to be empty, running migrations."
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/scripts/setup-db.js
yarn database:migrate:prod
yarn database:init:prod
fi
yarn command:prod cache:flush
@@ -0,0 +1,205 @@
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type View } from '@/views/types/View';
import {
NavigationMenuItemType,
ViewKey,
type NavigationMenuItem,
} from '~/generated-metadata/graphql';
import { getNavigationMenuItemLabel } from '@/navigation-menu-item/display/utils/getNavigationMenuItemLabel';
type ObjectMetadata = Pick<
EnrichedObjectMetadataItem,
'id' | 'labelPlural' | 'nameSingular'
>;
type ViewMetadata = Pick<View, 'id' | 'name' | 'objectMetadataId' | 'key'>;
const objectMetadataItems: ObjectMetadata[] = [
{ id: 'obj-1', labelPlural: 'Notes', nameSingular: 'note' },
{ id: 'obj-2', labelPlural: 'Companies', nameSingular: 'company' },
];
const views: ViewMetadata[] = [
{
id: 'view-index',
name: 'All Notes',
objectMetadataId: 'obj-1',
key: ViewKey.INDEX,
},
{
id: 'view-custom',
name: 'My Custom View',
objectMetadataId: 'obj-1',
key: null,
},
];
const baseItem: NavigationMenuItem = {
id: 'nav-1',
type: NavigationMenuItemType.OBJECT,
position: 0,
createdAt: '',
updatedAt: '',
};
describe('getNavigationMenuItemLabel', () => {
describe('when type is OBJECT', () => {
it('should return labelPlural for a matching object', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.OBJECT,
targetObjectMetadataId: 'obj-1',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'Notes',
);
});
it('should return empty string when object is not found', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.OBJECT,
targetObjectMetadataId: 'nonexistent',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'',
);
});
});
describe('when type is VIEW', () => {
it('should return the resolved view name for an INDEX view', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.VIEW,
viewId: 'view-index',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'All Notes',
);
});
it('should return the view name for a non-INDEX view', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.VIEW,
viewId: 'view-custom',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'My Custom View',
);
});
it('should return empty string when view is not found', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.VIEW,
viewId: 'nonexistent',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'',
);
});
});
describe('when type is LINK', () => {
it('should return the item name when present', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.LINK,
name: 'Documentation',
link: 'https://docs.example.com',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'Documentation',
);
});
it('should return the link URL when name is null', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.LINK,
name: null,
link: 'https://docs.example.com',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'https://docs.example.com',
);
});
it('should return "Link" when both name and link are empty', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.LINK,
name: null,
link: ' ',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'Link',
);
});
});
describe('when type is RECORD', () => {
it('should return labelIdentifier from targetRecordIdentifier', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.RECORD,
targetRecordIdentifier: {
id: 'rec-1',
labelIdentifier: 'Acme Corp',
},
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'Acme Corp',
);
});
it('should return empty string when targetRecordIdentifier is missing', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.RECORD,
targetRecordIdentifier: null,
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'',
);
});
});
describe('when type is FOLDER', () => {
it('should return the item name when present', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.FOLDER,
name: 'Sales',
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'Sales',
);
});
it('should return "Folder" when name is null', () => {
const item = {
...baseItem,
type: NavigationMenuItemType.FOLDER,
name: null,
};
expect(getNavigationMenuItemLabel(item, objectMetadataItems, views)).toBe(
'Folder',
);
});
});
});
@@ -1,9 +1,10 @@
import { getLinkNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/link/utils/getLinkNavigationMenuItemComputedLink';
import { getObjectNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/object/utils/getObjectNavigationMenuItemComputedLink';
import { getRecordNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/record/utils/getRecordNavigationMenuItemComputedLink';
import { getViewNavigationMenuItemComputedLink } from '@/navigation-menu-item/display/view/utils/getViewNavigationMenuItemComputedLink';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/common/utils/recordIdentifierToObjectRecordIdentifier';
import { type View } from '@/views/types/View';
import { ViewKey } from '@/views/types/ViewKey';
import { AppPath, NavigationMenuItemType } from 'twenty-shared/types';
import { getAppPath, isDefined } from 'twenty-shared/utils';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
export const getNavigationMenuItemComputedLink = (
@@ -12,64 +13,22 @@ export const getNavigationMenuItemComputedLink = (
views: Pick<View, 'id' | 'objectMetadataId' | 'key'>[],
): string => {
switch (item.type) {
case NavigationMenuItemType.OBJECT: {
const objectMetadataItem = objectMetadataItems.find(
(meta) => meta.id === item.targetObjectMetadataId,
case NavigationMenuItemType.OBJECT:
return getObjectNavigationMenuItemComputedLink(
item,
objectMetadataItems,
views,
);
if (!isDefined(objectMetadataItem)) {
return '';
}
const indexView = views.find(
(view) =>
view.objectMetadataId === objectMetadataItem.id &&
view.key === ViewKey.INDEX,
case NavigationMenuItemType.VIEW:
return getViewNavigationMenuItemComputedLink(
item,
objectMetadataItems,
views,
);
return getAppPath(
AppPath.RecordIndexPage,
{ objectNamePlural: objectMetadataItem.namePlural },
indexView ? { viewId: indexView.id } : {},
);
}
case NavigationMenuItemType.VIEW: {
const view = views.find((view) => view.id === item.viewId);
if (!isDefined(view)) {
return '';
}
const objectMetadataItem = objectMetadataItems.find(
(meta) => meta.id === view.objectMetadataId,
);
if (!isDefined(objectMetadataItem)) {
return '';
}
return getAppPath(
AppPath.RecordIndexPage,
{ objectNamePlural: objectMetadataItem.namePlural },
{ viewId: item.viewId! },
);
}
case NavigationMenuItemType.LINK: {
const linkUrl = (item.link ?? '').trim();
if (linkUrl.startsWith('http://') || linkUrl.startsWith('https://')) {
return linkUrl;
}
return linkUrl ? `https://${linkUrl}` : '';
}
case NavigationMenuItemType.RECORD: {
const objectMetadataItem = objectMetadataItems.find(
(meta) => meta.id === item.targetObjectMetadataId,
);
if (
!isDefined(objectMetadataItem) ||
!isDefined(item.targetRecordIdentifier)
) {
return '';
}
const objectRecordIdentifier = recordIdentifierToObjectRecordIdentifier({
recordIdentifier: item.targetRecordIdentifier,
objectMetadataItem,
});
return objectRecordIdentifier.linkToShowPage ?? '';
}
case NavigationMenuItemType.LINK:
return getLinkNavigationMenuItemComputedLink(item);
case NavigationMenuItemType.RECORD:
return getRecordNavigationMenuItemComputedLink(item, objectMetadataItems);
default:
return '';
}
@@ -1,8 +1,11 @@
import { getFolderNavigationMenuItemLabel } from '@/navigation-menu-item/display/folder/utils/getFolderNavigationMenuItemLabel';
import { getLinkNavigationMenuItemLabel } from '@/navigation-menu-item/display/link/utils/getLinkNavigationMenuItemLabel';
import { getObjectNavigationMenuItemLabel } from '@/navigation-menu-item/display/object/utils/getObjectNavigationMenuItemLabel';
import { getRecordNavigationMenuItemLabel } from '@/navigation-menu-item/display/record/utils/getRecordNavigationMenuItemLabel';
import { getViewNavigationMenuItemLabel } from '@/navigation-menu-item/display/view/utils/getViewNavigationMenuItemLabel';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type View } from '@/views/types/View';
import { ViewKey } from '@/views/types/ViewKey';
import { NavigationMenuItemType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
export const getNavigationMenuItemLabel = (
@@ -14,35 +17,16 @@ export const getNavigationMenuItemLabel = (
views: Pick<View, 'id' | 'name' | 'objectMetadataId' | 'key'>[],
): string => {
switch (item.type) {
case NavigationMenuItemType.OBJECT: {
const objectMetadataItem = objectMetadataItems.find(
(meta) => meta.id === item.targetObjectMetadataId,
);
return objectMetadataItem?.labelPlural ?? '';
}
case NavigationMenuItemType.VIEW: {
const view = views.find((view) => view.id === item.viewId);
if (!isDefined(view)) {
return '';
}
if (view.key === ViewKey.INDEX) {
const objectMetadataItem = objectMetadataItems.find(
(meta) => meta.id === view.objectMetadataId,
);
return objectMetadataItem?.labelPlural ?? view.name;
}
return view.name;
}
case NavigationMenuItemType.LINK: {
const linkUrl = (item.link ?? '').trim();
return (item.name ?? linkUrl) || 'Link';
}
case NavigationMenuItemType.RECORD: {
return item.targetRecordIdentifier?.labelIdentifier ?? '';
}
case NavigationMenuItemType.FOLDER: {
return item.name ?? 'Folder';
}
case NavigationMenuItemType.OBJECT:
return getObjectNavigationMenuItemLabel(item, objectMetadataItems);
case NavigationMenuItemType.VIEW:
return getViewNavigationMenuItemLabel(item, views);
case NavigationMenuItemType.LINK:
return getLinkNavigationMenuItemLabel(item);
case NavigationMenuItemType.RECORD:
return getRecordNavigationMenuItemLabel(item);
case NavigationMenuItemType.FOLDER:
return getFolderNavigationMenuItemLabel(item);
default:
return item.name ?? '';
}
@@ -1,23 +1,14 @@
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type View } from '@/views/types/View';
import { ViewKey } from '@/views/types/ViewKey';
import { isDefined } from 'twenty-shared/utils';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
export const getViewNavigationMenuItemLabel = (
item: Pick<NavigationMenuItem, 'viewId'>,
views: Pick<View, 'id' | 'name' | 'objectMetadataId' | 'key'>[],
objectMetadataItems: Pick<EnrichedObjectMetadataItem, 'id' | 'labelPlural'>[],
): string => {
const view = views.find((view) => view.id === item.viewId);
if (!isDefined(view)) {
return '';
}
if (view.key === ViewKey.INDEX) {
const objectMetadataItem = objectMetadataItems.find(
(meta) => meta.id === view.objectMetadataId,
);
return objectMetadataItem?.labelPlural ?? view.name;
}
return view.name;
};
+2 -2
View File
@@ -9,8 +9,8 @@
"start:prod": "node dist/main",
"command:prod": "node dist/command/command",
"worker:prod": "node dist/queue-worker/queue-worker",
"database:init:prod": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate:prod",
"database:migrate:prod": "npx -y typeorm migration:run -d dist/database/typeorm/core/core.datasource",
"database:init:prod": "node dist/database/scripts/setup-db.js && yarn database:migrate:prod --force",
"database:migrate:prod": "node dist/command/command run-typeorm-migration",
"clickhouse:migrate:prod": "node dist/database/clickHouse/migrations/run-migrations.js",
"typeorm": "../../node_modules/typeorm/.bin/typeorm"
},
+4 -4
View File
@@ -246,8 +246,8 @@
"no-seed": {
"cwd": "packages/twenty-server",
"commands": [
"nx ts-node-no-deps-transpile-only -- ./scripts/truncate-db.ts",
"nx ts-node-no-deps-transpile-only -- ./scripts/setup-db.ts",
"node dist/database/scripts/truncate-db.js",
"node dist/database/scripts/setup-db.js",
"nx database:migrate",
"nx command-no-deps -- cache:flush"
],
@@ -256,8 +256,8 @@
"seed": {
"cwd": "packages/twenty-server",
"commands": [
"nx ts-node-no-deps-transpile-only -- ./scripts/truncate-db.ts",
"nx ts-node-no-deps-transpile-only -- ./scripts/setup-db.ts",
"node dist/database/scripts/truncate-db.js",
"node dist/database/scripts/setup-db.js",
"nx database:migrate",
"nx command-no-deps -- cache:flush",
"nx command-no-deps -- workspace:seed:dev"
@@ -1,21 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`UpgradeCommandRunner Workspace upgrade should fail when APP_VERSION is not defined 1`] = `[Error: Cannot run upgrade command when APP_VERSION is not defined, please double check your env variables]`;
exports[`UpgradeCommandRunner Workspace upgrade should fail when all commands contains invalid semver keys 1`] = `[Error: No previous version found for version 2.0.0. Please review the "allCommands" record. Available versions are: invalid, 2.0.0]`;
exports[`UpgradeCommandRunner Workspace upgrade should fail when current version commands are not found 1`] = `[Error: No command found for version 42.0.0. Please check the commands record.]`;
exports[`UpgradeCommandRunner Workspace upgrade should fail when previous version is not found 1`] = `[Error: No previous version found for version 1.0.0. Please review the "allCommands" record. Available versions are: 1.0.0, 2.0.0]`;
exports[`UpgradeCommandRunner Workspace upgrade should fail when workspace version is not defined 1`] = `
[Error: Unable to run the upgrade command. Aborting the upgrade process.
Please ensure that all workspaces are on at least the previous minor version (1.0.0).
If any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.]
`;
exports[`UpgradeCommandRunner Workspace upgrade should fail when workspace version is not equal to fromVersion 1`] = `
[Error: Unable to run the upgrade command. Aborting the upgrade process.
Please ensure that all workspaces are on at least the previous minor version (1.0.0).
If any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.]
`;
@@ -7,35 +7,41 @@ import {
} from 'twenty-shared/testing';
import { type Repository } from 'typeorm';
import { UpgradeCommandRunner } from 'src/database/commands/command-runners/upgrade.command-runner';
import {
UpgradeCommandRunner,
type AllCommands,
} from 'src/database/commands/command-runners/upgrade.command-runner';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { UPGRADE_COMMAND_SUPPORTED_VERSIONS } from 'src/engine/constants/upgrade-command-supported-versions.constant';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { type ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
const CURRENT_VERSION =
UPGRADE_COMMAND_SUPPORTED_VERSIONS[
UPGRADE_COMMAND_SUPPORTED_VERSIONS.length - 1
];
const PREVIOUS_VERSION =
UPGRADE_COMMAND_SUPPORTED_VERSIONS[
UPGRADE_COMMAND_SUPPORTED_VERSIONS.length - 2
];
class BasicUpgradeCommandRunner extends UpgradeCommandRunner {
allCommands = {
'1.0.0': [],
'2.0.0': [],
};
allCommands = Object.fromEntries(
UPGRADE_COMMAND_SUPPORTED_VERSIONS.map((version) => [version, []]),
) as unknown as AllCommands;
}
class InvalidUpgradeCommandRunner extends UpgradeCommandRunner {
allCommands = {
invalid: [],
'2.0.0': [],
};
}
type CommandRunnerValues =
| typeof BasicUpgradeCommandRunner
| typeof InvalidUpgradeCommandRunner;
type CommandRunnerValues = typeof BasicUpgradeCommandRunner;
const generateMockWorkspace = (overrides?: Partial<WorkspaceEntity>) =>
({
id: 'workspace-id',
version: '1.0.0',
version: PREVIOUS_VERSION,
createdAt: new Date(),
updatedAt: new Date(),
allowImpersonation: false,
@@ -73,12 +79,18 @@ const buildUpgradeCommandModule = async ({
twentyConfigService: TwentyConfigService,
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
dataSourceService: DataSourceService,
coreEngineVersionService: CoreEngineVersionService,
workspaceVersionService: WorkspaceVersionService,
coreMigrationRunnerService: CoreMigrationRunnerService,
) => {
return new commandRunner(
workspaceRepository,
twentyConfigService,
globalWorkspaceOrmManager,
dataSourceService,
coreEngineVersionService,
workspaceVersionService,
coreMigrationRunnerService,
);
},
inject: [
@@ -86,6 +98,9 @@ const buildUpgradeCommandModule = async ({
TwentyConfigService,
GlobalWorkspaceOrmManager,
DataSourceService,
CoreEngineVersionService,
WorkspaceVersionService,
CoreMigrationRunnerService,
],
},
{
@@ -98,6 +113,7 @@ const buildUpgradeCommandModule = async ({
),
update: jest.fn(),
find: jest.fn().mockResolvedValue(workspaces),
exists: jest.fn().mockResolvedValue(workspaces.length > 0),
},
},
{
@@ -130,6 +146,12 @@ const buildUpgradeCommandModule = async ({
provide: DataSourceService,
useValue: mockDataSourceService,
},
CoreEngineVersionService,
WorkspaceVersionService,
{
provide: CoreMigrationRunnerService,
useValue: { run: jest.fn().mockResolvedValue(undefined) },
},
],
}).compile();
@@ -139,7 +161,7 @@ const buildUpgradeCommandModule = async ({
describe('UpgradeCommandRunner', () => {
let upgradeCommandRunner: BasicUpgradeCommandRunner;
let workspaceRepository: Repository<WorkspaceEntity>;
let runCoreMigrationsSpy: jest.SpyInstance;
let coreMigrationRunnerService: CoreMigrationRunnerService;
type BuildModuleAndSetupSpiesArgs = {
numberOfWorkspace?: number;
@@ -153,7 +175,7 @@ describe('UpgradeCommandRunner', () => {
workspaceOverride,
workspaces,
commandRunner = BasicUpgradeCommandRunner,
appVersion = '2.0.0',
appVersion = CURRENT_VERSION,
}: BuildModuleAndSetupSpiesArgs) => {
const generatedWorkspaces = Array.from(
{ length: numberOfWorkspace },
@@ -176,9 +198,8 @@ describe('UpgradeCommandRunner', () => {
jest.spyOn(upgradeCommandRunner['logger'], 'warn').mockImplementation();
jest.spyOn(upgradeCommandRunner, 'runOnWorkspace');
runCoreMigrationsSpy = jest
.spyOn(upgradeCommandRunner, 'runCoreMigrations')
.mockImplementation(() => Promise.resolve());
coreMigrationRunnerService = module.get(CoreMigrationRunnerService);
workspaceRepository = module.get<Repository<WorkspaceEntity>>(
getRepositoryToken(WorkspaceEntity),
@@ -190,12 +211,10 @@ describe('UpgradeCommandRunner', () => {
id: 'higher_version_workspace',
version: '42.42.42',
});
const appVersion = '2.0.0';
await buildModuleAndSetupSpies({
numberOfWorkspace: 0,
workspaces: [higherVersionWorkspace],
appVersion,
});
// @ts-expect-error legacy noImplicitAny
const passedParams = [];
@@ -221,11 +240,9 @@ describe('UpgradeCommandRunner', () => {
it('should run upgrade over several workspaces', async () => {
const numberOfWorkspace = 42;
const appVersion = '2.0.0';
await buildModuleAndSetupSpies({
numberOfWorkspace,
appVersion,
});
// @ts-expect-error legacy noImplicitAny
const passedParams = [];
@@ -240,7 +257,7 @@ describe('UpgradeCommandRunner', () => {
expect(workspaceRepository.update).toHaveBeenNthCalledWith(
numberOfWorkspace,
{ id: expect.any(String) },
{ version: appVersion },
{ version: CURRENT_VERSION },
);
expect(upgradeCommandRunner.migrationReport.success.length).toBe(42);
expect(upgradeCommandRunner.migrationReport.fail.length).toBe(0);
@@ -254,9 +271,9 @@ describe('UpgradeCommandRunner', () => {
title: 'even if workspace version and app version differ in patch',
context: {
input: {
appVersion: 'v2.0.0',
appVersion: `v${CURRENT_VERSION}`,
workspaceOverride: {
version: 'v1.0.12',
version: `v${PREVIOUS_VERSION.replace('.0', '.12')}`,
},
},
},
@@ -266,9 +283,9 @@ describe('UpgradeCommandRunner', () => {
'even if workspace version and app version differ in patch and semantic',
context: {
input: {
appVersion: 'v2.0.0',
appVersion: `v${CURRENT_VERSION}`,
workspaceOverride: {
version: '1.0.12',
version: PREVIOUS_VERSION.replace('.0', '.12'),
},
},
},
@@ -277,9 +294,9 @@ describe('UpgradeCommandRunner', () => {
title: 'even if app version contains a patch value',
context: {
input: {
appVersion: '2.0.24',
appVersion: CURRENT_VERSION.replace('.0', '.24'),
workspaceOverride: {
version: '1.0.12',
version: PREVIOUS_VERSION.replace('.0', '.12'),
},
},
},
@@ -303,7 +320,7 @@ describe('UpgradeCommandRunner', () => {
expect(failReport.length).toBe(0);
expect(successReport.length).toBe(1);
expect(runCoreMigrationsSpy).toHaveBeenCalledTimes(1);
expect(coreMigrationRunnerService.run).toHaveBeenCalledTimes(1);
const { workspaceId } = successReport[0];
expect(workspaceId).toBe('workspace_0');
@@ -316,19 +333,20 @@ describe('UpgradeCommandRunner', () => {
input: Omit<BuildModuleAndSetupSpiesArgs, 'numberOfWorkspace'>;
output?: {
failReportWorkspaceId: string;
expectedErrorMessage: string;
};
}>[] = [
{
title: 'when workspace version is not equal to fromVersion',
context: {
input: {
appVersion: '2.0.0',
workspaceOverride: {
version: '0.1.0',
},
},
output: {
failReportWorkspaceId: 'workspace_0',
expectedErrorMessage: `Unable to run the upgrade command. Aborting the upgrade process.\nPlease ensure that all workspaces are on at least the previous minor version (${PREVIOUS_VERSION}).\nIf any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.`,
},
},
},
@@ -342,6 +360,7 @@ describe('UpgradeCommandRunner', () => {
},
output: {
failReportWorkspaceId: 'workspace_0',
expectedErrorMessage: `Unable to run the upgrade command. Aborting the upgrade process.\nPlease ensure that all workspaces are on at least the previous minor version (${PREVIOUS_VERSION}).\nIf any workspaces are not on the previous minor version, roll back to that version and run the upgrade command again.`,
},
},
},
@@ -353,6 +372,8 @@ describe('UpgradeCommandRunner', () => {
},
output: {
failReportWorkspaceId: 'global',
expectedErrorMessage:
'APP_VERSION is not defined, please double check your env variables',
},
},
},
@@ -364,6 +385,8 @@ describe('UpgradeCommandRunner', () => {
},
output: {
failReportWorkspaceId: 'global',
expectedErrorMessage:
'No command found for version 42.0.0. Please check the commands record.',
},
},
},
@@ -371,15 +394,11 @@ describe('UpgradeCommandRunner', () => {
title: 'when previous version is not found',
context: {
input: {
appVersion: '1.0.0',
appVersion: UPGRADE_COMMAND_SUPPORTED_VERSIONS[0],
},
},
},
{
title: 'when all commands contains invalid semver keys',
context: {
input: {
commandRunner: InvalidUpgradeCommandRunner,
output: {
failReportWorkspaceId: 'global',
expectedErrorMessage: `No previous version found for version ${UPGRADE_COMMAND_SUPPORTED_VERSIONS[0]}. Available versions: ${UPGRADE_COMMAND_SUPPORTED_VERSIONS.join(', ')}`,
},
},
},
@@ -403,7 +422,7 @@ describe('UpgradeCommandRunner', () => {
const { workspaceId, error } = failReport[0];
expect(workspaceId).toBe(output?.failReportWorkspaceId ?? 'global');
expect(error).toMatchSnapshot();
expect(error).toEqual(new Error(output?.expectedErrorMessage ?? ''));
},
);
});
@@ -1,13 +1,9 @@
import { InjectRepository } from '@nestjs/typeorm';
import { exec } from 'child_process';
import { promisify } from 'util';
import chalk from 'chalk';
import { SemVer } from 'semver';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { In, Repository } from 'typeorm';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
@@ -17,22 +13,24 @@ import {
RunOnWorkspaceArgs,
WorkspacesMigrationCommandRunner,
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { type UpgradeCommandVersion } from 'src/engine/constants/upgrade-command-supported-versions.constant';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
import {
type CompareVersionMajorAndMinorReturnType,
compareVersionMajorAndMinor,
} from 'src/utils/version/compare-version-minor-and-major';
import { getPreviousVersion } from 'src/utils/version/get-previous-version';
export type VersionCommands = (
| WorkspacesMigrationCommandRunner
| ActiveOrSuspendedWorkspacesMigrationCommandRunner
)[];
export type AllCommands = Record<string, VersionCommands>;
const execPromise = promisify(exec);
export type AllCommands = Record<UpgradeCommandVersion, VersionCommands>;
export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
private fromWorkspaceVersion: SemVer;
@@ -47,95 +45,13 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
protected readonly twentyConfigService: TwentyConfigService,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly coreEngineVersionService: CoreEngineVersionService,
protected readonly workspaceVersionService: WorkspaceVersionService,
protected readonly coreMigrationRunnerService: CoreMigrationRunnerService,
) {
super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService);
}
private async loadActiveOrSuspendedWorkspace() {
return await this.workspaceRepository.find({
select: ['id', 'version', 'displayName'],
where: {
activationStatus: In([
WorkspaceActivationStatus.ACTIVE,
WorkspaceActivationStatus.SUSPENDED,
]),
},
order: {
id: 'ASC',
},
});
}
private async shouldSkipUpgradeIfFreshInstallation(): Promise<boolean> {
const activeWorkspaceOrSuspendedWorkspaceCount =
await this.loadActiveOrSuspendedWorkspace();
return activeWorkspaceOrSuspendedWorkspaceCount.length === 0;
}
async runCoreMigrations(): Promise<void> {
this.logger.log('Running global database migrations');
try {
this.logger.log('Running core datasource migrations...');
const coreResult = await execPromise(
'npx -y typeorm migration:run -d dist/database/typeorm/core/core.datasource',
);
this.logger.log(coreResult.stdout);
this.logger.log('Database migrations completed successfully');
} catch (error) {
this.logger.error('Error running database migrations:', error);
throw error;
}
}
private async workspacesThatAreBelowFromWorkspaceVersion(
fromWorkspaceVersion: SemVer,
): Promise<Pick<WorkspaceEntity, 'id' | 'displayName' | 'version'>[]> {
try {
const allActiveOrSuspendedWorkspaces =
await this.loadActiveOrSuspendedWorkspace();
if (allActiveOrSuspendedWorkspaces.length === 0) {
this.logger.log(
'No workspaces found. Running migrations for fresh installation.',
);
return [];
}
const workspacesThatAreBelowFromWorkspaceVersion =
allActiveOrSuspendedWorkspaces.filter((workspace) => {
if (!isDefined(workspace.version)) {
return true;
}
try {
const versionCompareResult = compareVersionMajorAndMinor(
workspace.version,
fromWorkspaceVersion.version,
);
return versionCompareResult === 'lower';
} catch (error) {
this.logger.error(
`Error checking workspace ${workspace.id} version: ${error.message}`,
);
return true;
}
});
return workspacesThatAreBelowFromWorkspaceVersion;
} catch (error) {
this.logger.error('Error checking workspaces below version:', error);
throw error;
}
}
private setUpgradeContextVersionsAndCommandsForCurrentAppVersion() {
const upgradeContextIsAlreadyDefined = [
this.currentAppVersion,
@@ -147,8 +63,9 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
return;
}
const currentAppVersion = this.retrieveCurrentAppVersion();
const currentVersionMajorMinor = `${currentAppVersion.major}.${currentAppVersion.minor}.0`;
const currentAppVersion = this.coreEngineVersionService.getCurrentVersion();
const currentVersionMajorMinor =
`${currentAppVersion.major}.${currentAppVersion.minor}.0` as UpgradeCommandVersion;
const currentCommands = this.allCommands[currentVersionMajorMinor];
if (!isDefined(currentCommands)) {
@@ -157,17 +74,8 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
);
}
const allCommandsVersions = Object.keys(this.allCommands);
const previousVersion = getPreviousVersion({
currentVersion: currentVersionMajorMinor,
versions: allCommandsVersions,
});
const previousVersion = this.coreEngineVersionService.getPreviousVersion();
if (!isDefined(previousVersion)) {
throw new Error(
`No previous version found for version ${currentAppVersion}. Please review the "allCommands" record. Available versions are: ${allCommandsVersions.join(', ')}`,
);
}
this.commands = currentCommands;
this.fromWorkspaceVersion = previousVersion;
this.currentAppVersion = currentAppVersion;
@@ -189,10 +97,12 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
try {
this.setUpgradeContextVersionsAndCommandsForCurrentAppVersion();
const shouldSkipUpgradeIfFreshInstallation =
await this.shouldSkipUpgradeIfFreshInstallation();
// On fresh installs there are no workspaces yet, so skip the
// per-workspace upgrade loop (core migrations already ran above).
const hasWorkspaces =
await this.workspaceVersionService.hasActiveOrSuspendedWorkspaces();
if (shouldSkipUpgradeIfFreshInstallation) {
if (!hasWorkspaces) {
this.logger.log(
chalk.blue('Fresh installation detected, skipping migration'),
);
@@ -201,8 +111,8 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
}
const workspacesThatAreBelowFromWorkspaceVersion =
await this.workspacesThatAreBelowFromWorkspaceVersion(
this.fromWorkspaceVersion,
await this.workspaceVersionService.getWorkspacesBelowVersion(
this.fromWorkspaceVersion.version,
);
if (workspacesThatAreBelowFromWorkspaceVersion.length > 0) {
@@ -234,7 +144,7 @@ If any workspaces are not on the previous minor version, roll back to that versi
return;
}
await this.runCoreMigrations();
await this.coreMigrationRunnerService.run();
await super.runMigrationCommand(passedParams, options);
}
@@ -295,24 +205,6 @@ If any workspaces are not on the previous minor version, roll back to that versi
}
}
private retrieveCurrentAppVersion() {
const appVersion = this.twentyConfigService.get('APP_VERSION');
if (!isDefined(appVersion)) {
throw new Error(
'Cannot run upgrade command when APP_VERSION is not defined, please double check your env variables',
);
}
try {
return new SemVer(appVersion);
} catch {
throw new Error(
`Should never occur, APP_VERSION is invalid ${appVersion}`,
);
}
}
private async retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion(
workspaceId: string,
): Promise<CompareVersionMajorAndMinorReturnType> {
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
@Module({
providers: [CoreMigrationRunnerService],
exports: [CoreMigrationRunnerService],
})
export class CoreMigrationRunnerModule {}
@@ -0,0 +1,37 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
@Injectable()
export class CoreMigrationRunnerService {
private readonly logger = new Logger(CoreMigrationRunnerService.name);
constructor(
@InjectDataSource()
private readonly dataSource: DataSource,
) {}
async run(): Promise<void> {
this.logger.log('Running core datasource migrations...');
try {
const migrations = await this.dataSource.runMigrations({
transaction: 'each',
});
if (migrations.length === 0) {
this.logger.log('No pending migrations');
} else {
this.logger.log(
`Executed ${migrations.length} migration(s): ${migrations.map((migration) => migration.name).join(', ')}`,
);
}
this.logger.log('Database migrations completed successfully');
} catch (error) {
this.logger.error('Error running database migrations:', error);
throw error;
}
}
}
@@ -5,9 +5,12 @@ import { CronRegisterAllCommand } from 'src/database/commands/cron-register-all.
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
import { ListOrphanedWorkspaceEntitiesCommand } from 'src/database/commands/list-and-delete-orphaned-workspace-entities.command';
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
import { WorkspaceExportModule } from 'src/database/commands/workspace-export/workspace-export.module';
import { RunTypeormMigrationCommand } from 'src/database/commands/run-typeorm-migration.command';
import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module';
import { WorkspaceExportModule } from 'src/database/commands/workspace-export/workspace-export.module';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { CoreEngineVersionModule } from 'src/engine/core-engine-version/core-engine-version.module';
import { CoreMigrationRunnerModule } from 'src/database/commands/core-migration-runner/core-migration-runner.module';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
@@ -31,6 +34,7 @@ import { DevSeederModule } from 'src/engine/workspace-manager/dev-seeder/dev-see
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
import { CalendarEventImportManagerModule } from 'src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module';
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
import { WorkflowRunQueueModule } from 'src/modules/workflow/workflow-runner/workflow-run-queue/workflow-run-queue.module';
@@ -68,6 +72,9 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
MarketplaceModule,
ApplicationUpgradeModule,
StaleRegistrationCleanupModule,
CoreEngineVersionModule,
CoreMigrationRunnerModule,
WorkspaceVersionModule,
],
providers: [
DataSeedWorkspaceCommand,
@@ -76,6 +83,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
ListOrphanedWorkspaceEntitiesCommand,
EnterpriseKeyValidationCronCommand,
GenerateApiKeyCommand,
RunTypeormMigrationCommand,
],
})
export class DatabaseCommandModule {}
@@ -0,0 +1,75 @@
import { Logger } from '@nestjs/common';
import chalk from 'chalk';
import { Command, CommandRunner, Option } from 'nest-commander';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
type RunTypeormMigrationCommandOptions = {
force?: boolean;
};
@Command({
name: 'run-typeorm-migration',
description:
'Run TypeORM core migrations with workspace version safety check',
})
export class RunTypeormMigrationCommand extends CommandRunner {
private readonly logger = new Logger(RunTypeormMigrationCommand.name);
constructor(
private readonly coreEngineVersionService: CoreEngineVersionService,
private readonly workspaceVersionService: WorkspaceVersionService,
private readonly coreMigrationRunnerService: CoreMigrationRunnerService,
) {
super();
}
@Option({
flags: '-f, --force',
description: 'Skip workspace version safety check',
required: false,
})
parseForce(): boolean {
return true;
}
async run(
_passedParams: string[],
options: RunTypeormMigrationCommandOptions,
): Promise<void> {
if (options.force) {
this.logger.warn(
chalk.yellow('Skipping workspace version check (--force flag used)'),
);
} else {
const previousVersion =
this.coreEngineVersionService.getPreviousVersion();
const workspacesBelow =
await this.workspaceVersionService.getWorkspacesBelowVersion(
previousVersion.version,
);
if (workspacesBelow.length > 0) {
for (const workspace of workspacesBelow) {
this.logger.error(
chalk.red(
`Workspace ${workspace.id} (${workspace.displayName}) is at version ${workspace.version ?? 'undefined'}, which is below the minimum required version.`,
),
);
}
throw new Error(
'Unable to run TypeORM migrations. Some workspace(s) are below the minimum required version.\n' +
'Please ensure all workspaces are on at least the previous minor version before running migrations.\n' +
'Use --force to bypass this check (not recommended).',
);
}
}
await this.coreMigrationRunnerService.run();
}
}
@@ -255,8 +255,11 @@ export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaces
const trigger = workflowVersion.trigger as WorkflowManualTrigger;
const { availabilityType, availabilityObjectMetadataId } =
await this.resolveManualTriggerAvailability(trigger, workspaceId);
const {
availabilityType,
availabilityObjectMetadataId,
availabilityObjectMetadataUniversalIdentifier,
} = await this.resolveManualTriggerAvailability(trigger, workspaceId);
const id = uuidv4();
const now = new Date().toISOString();
@@ -276,7 +279,7 @@ export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaces
hotKeys: null,
availabilityType,
availabilityObjectMetadataId: availabilityObjectMetadataId ?? null,
availabilityObjectMetadataUniversalIdentifier: null,
availabilityObjectMetadataUniversalIdentifier,
conditionalAvailabilityExpression: null,
workspaceId,
applicationId: workspaceCustomFlatApplication.id,
@@ -340,6 +343,7 @@ export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaces
): Promise<{
availabilityType: CommandMenuItemAvailabilityType;
availabilityObjectMetadataId: string | undefined;
availabilityObjectMetadataUniversalIdentifier: string | null;
}> {
const availability = trigger.settings.availability;
@@ -347,10 +351,11 @@ export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaces
return {
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: undefined,
availabilityObjectMetadataUniversalIdentifier: null,
};
}
const { objectIdByNameSingular } =
const { objectIdByNameSingular, flatObjectMetadataMaps } =
await this.workflowCommonWorkspaceService.getFlatEntityMaps(workspaceId);
const objectId = objectIdByNameSingular[availability.objectNameSingular];
@@ -363,12 +368,17 @@ export class BackfillCommandMenuItemsCommand extends ActiveOrSuspendedWorkspaces
return {
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
availabilityObjectMetadataId: undefined,
availabilityObjectMetadataUniversalIdentifier: null,
};
}
const objectUniversalIdentifier =
flatObjectMetadataMaps.universalIdentifierById[objectId] ?? null;
return {
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
availabilityObjectMetadataId: objectId,
availabilityObjectMetadataUniversalIdentifier: objectUniversalIdentifier,
};
}
}
@@ -229,6 +229,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
);
const {
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatPageLayoutMaps: existingFlatPageLayoutMaps,
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
@@ -236,6 +237,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatFieldMetadataMaps',
'flatPageLayoutMaps',
'flatPageLayoutTabMaps',
'flatPageLayoutWidgetMaps',
@@ -320,6 +322,15 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
allStandardFieldsWidgetViewUniversalIds.has(
viewField.viewUniversalIdentifier,
),
)
// Skip viewFields whose referenced fieldMetadata doesn't exist in the workspace yet
// (e.g. fields added to the standard definition but not yet synced to this workspace)
.filter((viewField) =>
isDefined(
existingFlatFieldMetadataMaps.byUniversalIdentifier[
viewField.fieldMetadataUniversalIdentifier
],
),
);
// Same delete-and-recreate approach for viewFieldGroups
@@ -6,8 +6,11 @@ import { V1_18_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
import { V1_19_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-19/1-19-upgrade-version-command.module';
import { V1_20_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module';
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
import { CoreEngineVersionModule } from 'src/engine/core-engine-version/core-engine-version.module';
import { CoreMigrationRunnerModule } from 'src/database/commands/core-migration-runner/core-migration-runner.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
@Module({
imports: [
@@ -17,6 +20,9 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
V1_19_UpgradeVersionCommandModule,
V1_20_UpgradeVersionCommandModule,
DataSourceModule,
CoreEngineVersionModule,
CoreMigrationRunnerModule,
WorkspaceVersionModule,
],
providers: [UpgradeCommand],
})
@@ -9,6 +9,7 @@ import {
UpgradeCommandRunner,
type VersionCommands,
} from 'src/database/commands/command-runners/upgrade.command-runner';
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { BackfillApplicationPackageFilesCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-backfill-application-package-files.command';
import { DeleteFileRecordsAndUpdateTableCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-delete-all-files-and-update-table.command';
import { FixMorphRelationFieldNamesCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-fix-morph-relation-field-names.command';
@@ -49,10 +50,12 @@ import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/co
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
@Command({
name: 'upgrade',
@@ -67,6 +70,9 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly twentyConfigService: TwentyConfigService,
protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
protected readonly coreEngineVersionService: CoreEngineVersionService,
protected readonly workspaceVersionService: WorkspaceVersionService,
protected readonly coreMigrationRunnerService: CoreMigrationRunnerService,
// 1.17 Commands
protected readonly backfillApplicationPackageFilesCommand: BackfillApplicationPackageFilesCommand,
@@ -121,6 +127,9 @@ export class UpgradeCommand extends UpgradeCommandRunner {
twentyConfigService,
globalWorkspaceOrmManager,
dataSourceService,
coreEngineVersionService,
workspaceVersionService,
coreMigrationRunnerService,
);
// Note: Required empty commands array to allow retrieving previous version
@@ -1,6 +1,6 @@
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
import { camelToSnakeCase, performQuery } from './utils';
import { camelToSnakeCase, performQuery } from './setup-db-utils';
rawDataSource
.initialize()
@@ -1,6 +1,6 @@
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
import { performQuery } from './utils';
import { performQuery } from './setup-db-utils';
async function dropSchemasSequentially() {
try {
@@ -23,8 +23,8 @@ import { useDirectExecution } from 'src/engine/api/graphql/direct-execution/hook
import { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.factory';
import { CoreEngineModule } from 'src/engine/core-modules/core-engine.module';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { useSentryTracing } from 'src/engine/core-modules/exception-handler/hooks/use-sentry-tracing';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { useDisableIntrospectionAndSuggestionsForUnauthenticatedUsers } from 'src/engine/core-modules/graphql/hooks/use-disable-introspection-and-suggestions-for-unauthenticated-users.hook';
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
import { useGraphQLQueryTiming } from 'src/engine/core-modules/graphql/hooks/use-graphql-query-timing.hook';
@@ -0,0 +1,10 @@
export const UPGRADE_COMMAND_SUPPORTED_VERSIONS = [
'1.16.0',
'1.17.0',
'1.18.0',
'1.19.0',
'1.20.0',
] as const;
export type UpgradeCommandVersion =
(typeof UPGRADE_COMMAND_SUPPORTED_VERSIONS)[number];
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CoreEngineVersionService } from 'src/engine/core-engine-version/services/core-engine-version.service';
@Module({
providers: [CoreEngineVersionService],
exports: [CoreEngineVersionService],
})
export class CoreEngineVersionModule {}
@@ -0,0 +1,47 @@
import { Injectable } from '@nestjs/common';
import { SemVer } from 'semver';
import { isDefined } from 'twenty-shared/utils';
import { UPGRADE_COMMAND_SUPPORTED_VERSIONS } from 'src/engine/constants/upgrade-command-supported-versions.constant';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { getPreviousVersion } from 'src/utils/version/get-previous-version';
@Injectable()
export class CoreEngineVersionService {
constructor(private readonly twentyConfigService: TwentyConfigService) {}
getCurrentVersion(): SemVer {
const appVersion = this.twentyConfigService.get('APP_VERSION');
if (!isDefined(appVersion)) {
throw new Error(
'APP_VERSION is not defined, please double check your env variables',
);
}
try {
return new SemVer(appVersion);
} catch {
throw new Error(`APP_VERSION is not a valid semver: "${appVersion}"`);
}
}
getPreviousVersion(): SemVer {
const currentAppVersion = this.getCurrentVersion();
const currentVersionMajorMinor = `${currentAppVersion.major}.${currentAppVersion.minor}.0`;
const previousVersion = getPreviousVersion({
currentVersion: currentVersionMajorMinor,
versions: [...UPGRADE_COMMAND_SUPPORTED_VERSIONS],
});
if (!isDefined(previousVersion)) {
throw new Error(
`No previous version found for version ${currentAppVersion}. Available versions: ${UPGRADE_COMMAND_SUPPORTED_VERSIONS.join(', ')}`,
);
}
return previousVersion;
}
}
@@ -0,0 +1 @@
export const MAX_WORKSPACES_WITHOUT_ENTERPRISE_KEY = 5;
@@ -97,6 +97,9 @@ const createSignInUpServiceForTests = () => {
{
uploadWorkspaceLogoFromUrl: jest.fn(),
} as any,
{
isValid: jest.fn().mockReturnValue(false),
} as any,
{
createQueryRunner: jest.fn(() => queryRunnerMock),
} as any,
@@ -9,6 +9,7 @@ import { Repository, type DataSource, type QueryRunner } from 'typeorm';
import { v4 } from 'uuid';
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
import { MAX_WORKSPACES_WITHOUT_ENTERPRISE_KEY } from 'src/engine/core-modules/auth/constants/max-workspaces-without-enterprise-key.constants';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import {
@@ -29,6 +30,7 @@ import {
type SignInUpNewUserPayload,
} from 'src/engine/core-modules/auth/types/signInUp.type';
import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service';
import { EnterprisePlanService } from 'src/engine/core-modules/enterprise/services/enterprise-plan.service';
import { FileCorePictureService } from 'src/engine/core-modules/file/file-core-picture/services/file-core-picture.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
@@ -65,6 +67,7 @@ export class SignInUpService {
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly applicationService: ApplicationService,
private readonly fileCorePictureService: FileCorePictureService,
private readonly enterprisePlanService: EnterprisePlanService,
@InjectDataSource()
private readonly dataSource: DataSource,
) {}
@@ -424,6 +427,8 @@ export class SignInUpService {
return;
}
await this.assertWorkspaceCountWithinLimit(workspaceCount);
if (
!this.twentyConfigService.get(
'IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS',
@@ -449,6 +454,26 @@ export class SignInUpService {
);
}
private async assertWorkspaceCountWithinLimit(
workspaceCount: number,
): Promise<void> {
if (this.enterprisePlanService.isValid()) {
return;
}
if (workspaceCount < MAX_WORKSPACES_WITHOUT_ENTERPRISE_KEY) {
return;
}
throw new AuthException(
`Cannot create more than ${MAX_WORKSPACES_WITHOUT_ENTERPRISE_KEY} workspaces without a valid enterprise key`,
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: msg`Workspace limit reached. A valid enterprise key is required to create more workspaces.`,
},
);
}
async signUpOnNewWorkspace(
userData: ExistingUserOrPartialUserWithPicture['userData'],
) {
@@ -42,6 +42,74 @@ describe('SubdomainManagerService', () => {
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
});
describe('getSubdomainAndDomainFromUrl', () => {
it('should extract subdomain from a full URL with protocol', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => {
const env = {
FRONTEND_URL: 'https://twenty.com',
DEFAULT_SUBDOMAIN: 'app',
};
// @ts-expect-error legacy noImplicitAny
return env[key];
});
const result =
domainServerConfigService.getSubdomainAndDomainFromUrl(
'https://myworkspace.twenty.com',
);
expect(result.subdomain).toBe('myworkspace');
expect(result.domain).toBeNull();
});
it('should handle URL without protocol scheme', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => {
const env = {
FRONTEND_URL: 'https://twenty.com',
DEFAULT_SUBDOMAIN: 'app',
};
// @ts-expect-error legacy noImplicitAny
return env[key];
});
const result =
domainServerConfigService.getSubdomainAndDomainFromUrl(
'myworkspace.twenty.com',
);
expect(result.subdomain).toBe('myworkspace');
expect(result.domain).toBeNull();
});
it('should return custom domain for non-front domain URLs', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => {
const env = {
FRONTEND_URL: 'https://twenty.com',
DEFAULT_SUBDOMAIN: 'app',
};
// @ts-expect-error legacy noImplicitAny
return env[key];
});
const result =
domainServerConfigService.getSubdomainAndDomainFromUrl(
'custom.example.com',
);
expect(result.subdomain).toBeUndefined();
expect(result.domain).toBe('custom.example.com');
});
});
describe('buildBaseUrl', () => {
it('should build the base URL from environment variables', () => {
jest
@@ -46,7 +46,9 @@ export class DomainServerConfigService {
}
getSubdomainAndDomainFromUrl = (url: string) => {
const { hostname: originHostname } = new URL(url);
const urlWithProtocol = url.includes('://') ? url : `https://${url}`;
const { hostname: originHostname } = new URL(urlWithProtocol);
const frontDomain = this.getFrontUrl().hostname;
@@ -1502,16 +1502,6 @@ msgstr "Kontakauto skeppingsbeleid"
msgid "Content cannot be empty"
msgstr "Inhoud kan nie leeg wees nie"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Konteks"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Werksruimte-ID en rol stem nie ooreen nie."
msgid "Workspace is not ready to welcome new members"
msgstr "Werksruimte is nie gereed om nuwe lede te verwelkom nie"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "سياسة إنشاء جهة الاتصال التلقائية"
msgid "Content cannot be empty"
msgstr "لا يمكن أن يكون المحتوى فارغًا"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "السياق"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "عدم تطابق بين معرّف مساحة العمل والدور."
msgid "Workspace is not ready to welcome new members"
msgstr "مساحة العمل ليست جاهزة للترحيب بأعضاء جدد"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Política d'autocreació de contactes"
msgid "Content cannot be empty"
msgstr "El contingut no pot estar buit"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Context"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "No coincideixen l'ID de l'espai de treball i el rol."
msgid "Workspace is not ready to welcome new members"
msgstr "L'espai de treball no està preparat per donar la benvinguda a nous membres"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Politika automatického vytváření kontaktů"
msgid "Content cannot be empty"
msgstr "Obsah nesmí být prázdný"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Kontext"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Nesoulad ID pracovního prostoru a role."
msgid "Workspace is not ready to welcome new members"
msgstr "Pracovní prostor není připraven přivítat nové členy"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr ""
msgid "Content cannot be empty"
msgstr "Indhold kan ikke være tomt"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Kontekst"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Uoverensstemmelse mellem arbejdsområde-ID og rolle."
msgid "Workspace is not ready to welcome new members"
msgstr "Arbejdsområdet er ikke klar til at byde nye medlemmer velkommen"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Richtlinie zur automatischen Kontakterstellung"
msgid "Content cannot be empty"
msgstr "Inhalt darf nicht leer sein"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Kontext"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Arbeitsbereichs-ID und Rolle stimmen nicht überein."
msgid "Workspace is not ready to welcome new members"
msgstr "Workspace ist nicht bereit, neue Mitglieder willkommen zu heißen"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Πολιτική Αυτόματης Δημιουργίας Επαφής"
msgid "Content cannot be empty"
msgstr "Το περιεχόμενο δεν μπορεί να είναι κενό"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Περιβάλλον"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Ασυμφωνία αναγνωριστικού χώρου εργασί
msgid "Workspace is not ready to welcome new members"
msgstr "Ο χώρος εργασίας δεν είναι έτοιμος να υποδεχθεί νέα μέλη"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1497,16 +1497,6 @@ msgstr "Contact auto creation policy"
msgid "Content cannot be empty"
msgstr "Content cannot be empty"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Context"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr "Context of the workflow run"
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5357,16 +5347,6 @@ msgstr "Other"
msgid "Outgoing"
msgstr "Outgoing"
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr "Output"
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr "Output of the workflow run"
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8365,6 +8345,11 @@ msgstr "Workspace ID and role mismatch."
msgid "Workspace is not ready to welcome new members"
msgstr "Workspace is not ready to welcome new members"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr "Workspace limit reached. A valid enterprise key is required to create more workspaces."
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Política de creación automática de contactos"
msgid "Content cannot be empty"
msgstr "El contenido no puede estar vacío"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Contexto"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "El ID del espacio de trabajo y el rol no coinciden."
msgid "Workspace is not ready to welcome new members"
msgstr "El espacio de trabajo no está listo para recibir nuevos miembros"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr ""
msgid "Content cannot be empty"
msgstr "Sisältö ei voi olla tyhjä"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Konteksti"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Työtilan tunnus ja rooli eivät vastaa toisiaan."
msgid "Workspace is not ready to welcome new members"
msgstr "Työtila ei ole valmis vastaanottamaan uusia jäseniä"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Politique de création automatique de contacts"
msgid "Content cannot be empty"
msgstr "Le contenu ne peut pas être vide"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Contexte"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Incompatibilité entre l'ID d'espace de travail et le rôle."
msgid "Workspace is not ready to welcome new members"
msgstr "L'espace de travail n'est pas prêt à accueillir de nouveaux membres"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1502,16 +1502,6 @@ msgstr "מדיניות יצירת אנשי קשר אוטומטית"
msgid "Content cannot be empty"
msgstr "התוכן לא יכול להיות ריק"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "הקשר"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "אי התאמה בין מזהה סביבת עבודה לתפקיד."
msgid "Workspace is not ready to welcome new members"
msgstr "מקום העבודה אינו מוכן לקבל חברים חדשים."
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Kapcsolattartó automatikus létrehozásának politikája"
msgid "Content cannot be empty"
msgstr "A tartalom nem lehet üres"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Kontextus"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "A munkaterület-azonosító és a szerepkör nem egyezik."
msgid "Workspace is not ready to welcome new members"
msgstr "A munkaterület nem áll készen új tagok fogadására"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Politica di creazione automatica dei contatti"
msgid "Content cannot be empty"
msgstr "Il contenuto non può essere vuoto"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Contesto"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "ID dello spazio di lavoro e ruolo non corrispondono."
msgid "Workspace is not ready to welcome new members"
msgstr "Lo spazio di lavoro non è pronto ad accogliere nuovi membri"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "連絡先自動作成ポリシー"
msgid "Content cannot be empty"
msgstr "コンテンツは空にできません"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "コンテキスト"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "ワークスペースIDとロールが一致しません。"
msgid "Workspace is not ready to welcome new members"
msgstr "ワークスペースは新しいメンバーを迎える準備ができていません"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "연락처 자동 생성 정책"
msgid "Content cannot be empty"
msgstr "내용은 비워 둘 수 없습니다"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "컨텍스트"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "워크스페이스 ID와 역할이 일치하지 않습니다."
msgid "Workspace is not ready to welcome new members"
msgstr "작업 공간은 새로운 멤버를 환영할 준비가 되어 있지 않습니다"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Beleid voor automatisch aanmaken van contacten"
msgid "Content cannot be empty"
msgstr "Inhoud mag niet leeg zijn"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Context"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Werkruimte-ID en rol komen niet overeen."
msgid "Workspace is not ready to welcome new members"
msgstr "Werkruimte is niet gereed om nieuwe leden te verwelkomen."
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Policy for automatisk oppretting av kontakter"
msgid "Content cannot be empty"
msgstr "Innhold kan ikke være tomt"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Kontekst"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Arbeidsområde-ID og rolle samsvarer ikke."
msgid "Workspace is not ready to welcome new members"
msgstr "Arbeidsområdet er ikke klart for å ta imot nye medlemmer"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Polityka automatycznego tworzenia kontaktu"
msgid "Content cannot be empty"
msgstr "Zawartość nie może być pusta"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Kontekst"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Niezgodność identyfikatora przestrzeni roboczej i roli."
msgid "Workspace is not ready to welcome new members"
msgstr "Workspace nie jest gotowy na przyjęcie nowych członków"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1497,16 +1497,6 @@ msgstr ""
msgid "Content cannot be empty"
msgstr ""
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr ""
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5357,16 +5347,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8365,6 +8345,11 @@ msgstr ""
msgid "Workspace is not ready to welcome new members"
msgstr ""
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr ""
msgid "Content cannot be empty"
msgstr "O conteúdo não pode estar vazio"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr ""
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Incompatibilidade entre o ID do espaço de trabalho e a função."
msgid "Workspace is not ready to welcome new members"
msgstr "O espaço de trabalho não está pronto para receber novos membros"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Política de Criação Automática de Contactos"
msgid "Content cannot be empty"
msgstr "O conteúdo não pode estar vazio"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Contexto"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Incompatibilidade entre ID do espaço de trabalho e função."
msgid "Workspace is not ready to welcome new members"
msgstr "Espaço de trabalho não está pronto para receber novos membros"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Politica de creare automată a contactelor"
msgid "Content cannot be empty"
msgstr "Conținutul nu poate fi gol"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Context"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Nepotrivire între ID-ul spațiului de lucru și rol."
msgid "Workspace is not ready to welcome new members"
msgstr "Spațiul de lucru nu este pregătit să primească noi membri"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Политика Автосоздания Контактов"
msgid "Content cannot be empty"
msgstr "Содержимое не может быть пустым"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Контекст"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Несоответствие идентификатора рабочег
msgid "Workspace is not ready to welcome new members"
msgstr "Рабочее пространство не готово принять новых участников"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Правила за аутоматско креирање контака
msgid "Content cannot be empty"
msgstr "Садржај не може бити празан"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Контекст"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Неподударање ID-ја радног простора и уло
msgid "Workspace is not ready to welcome new members"
msgstr "Радни простор није спреман за пријем нових чланова"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr "Policy för automatisk kontakt skapande"
msgid "Content cannot be empty"
msgstr "Innehåll får inte vara tomt"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Sammanhang"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5364,16 +5354,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8376,6 +8356,11 @@ msgstr "Arbetsyte-ID och roll stämmer inte överens."
msgid "Workspace is not ready to welcome new members"
msgstr "Arbetsutrymmet är inte redo att välkomna nya medlemmar"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr ""
msgid "Content cannot be empty"
msgstr "İçerik boş olamaz"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr "Bağlam"
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Çalışma alanı kimliği ile rol uyuşmuyor."
msgid "Workspace is not ready to welcome new members"
msgstr "Çalışma alanı yeni üyeleri ağırlamaya hazır değil"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"
@@ -1502,16 +1502,6 @@ msgstr ""
msgid "Content cannot be empty"
msgstr "Вміст не може бути порожнім"
#. js-lingui-id: M73whl
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context"
msgstr ""
#. js-lingui-id: YPzDDR
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Context of the workflow run"
msgstr ""
#. js-lingui-id: cmBz2p
#: src/engine/metadata-modules/object-permission/field-permission/field-permission.service.ts
msgid "Contradicting field permissions have been detected on a relation field."
@@ -5362,16 +5352,6 @@ msgstr ""
msgid "Outgoing"
msgstr ""
#. js-lingui-id: gh06VD
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output"
msgstr ""
#. js-lingui-id: 5vO0WN
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts
msgid "Output of the workflow run"
msgstr ""
#. js-lingui-id: 6ua+9g
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workspace-member-standard-flat-field-metadata.util.ts
msgid "Owned opportunities"
@@ -8370,6 +8350,11 @@ msgstr "Невідповідність між ідентифікатором р
msgid "Workspace is not ready to welcome new members"
msgstr "Робочий простір не готовий приймати нових учасників"
#. js-lingui-id: iN7doz
#: src/engine/core-modules/auth/services/sign-in-up.service.ts
msgid "Workspace limit reached. A valid enterprise key is required to create more workspaces."
msgstr ""
#. js-lingui-id: CbGxon
#: src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util.ts
msgid "Workspace member"

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