Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code d9aa0f9b5b chore: improve monitoring for fix(front): guard undefined recordNode in cache ma
Added code-level monitoring in `getRecordsFromRecordConnection` for malformed connection payloads: edges with missing `node` are now filtered out (to avoid downstream failures) and reported to Sentry as a warning-level message with grouped fingerprint and counts (`invalidEdgeCount`, `edgesCount`). This reduces noisy high-severity exception capture while preserving actionable signal about data-shape anomalies.
2026-05-12 09:48:51 +00:00
Sonarly Claude Code a9b7ec8cb0 fix(front): guard undefined recordNode in cache mapper
https://sonarly.com/issue/36844?type=bug

A frontend runtime error crashes a notes-page record picker flow when a record edge has an undefined node. The exception is handled by Sentry but leaves the user in a broken interaction state.

Fix: Implemented a defensive guard in `getRecordFromRecordNode` so undefined `recordNode` no longer throws during destructuring (`const { id, __typename } = recordNode`). The function now returns an empty object when `recordNode` is missing, preventing the runtime crash seen in Notes relation picker flow. Added a unit regression test and snapshot to lock this behavior.
2026-05-12 09:48:51 +00:00
Charles BochetandGitHub 5003fcbbf2 chore: remove dead feature flags (#20460)
## Summary

Two related cleanups, following the same pattern as #19916 and #19074.

### Dead feature flags

Drops four feature flags whose only references are the enum entry and
the generated GraphQL/SDK files:

- `IS_COMMAND_MENU_ITEM_ENABLED` — never read anywhere.
- `IS_DATASOURCE_MIGRATED` — already commented `@deprecated`. Zero
non-generated consumers.
- `IS_RICH_TEXT_V1_MIGRATED` — the 1-19 migration that gated it was
removed in #19074; the flag became dead at that point.
- `IS_CONNECTED_ACCOUNT_MIGRATED` — only read by the 1-21
`migrate-messaging-infrastructure-to-metadata` command as an
early-return guard, but the flag was never written anywhere in the
codebase, so the guard never fired (and that workspace command is now
removed entirely — see below).

Generated GraphQL/SDK schemas and the `workspace-entity-manager` test
mock are trimmed to match.

### 1-21 workspace commands

Same pattern as #19074 (which removed workspace commands ≤ 1.18). Twenty
is now on 2-5; the 1-21 workspace commands have long since run on every
active workspace and are dead code.

Removes:

- All 14 workspace commands under `upgrade-version-command/1-21/`
(compose-email menu item, key-value-pair index, datasource backfill,
message-thread backfill, dedup engine commands, select-all fixes, AI
response format migration, edit-layout label, drop messaging FKs, folder
parent-id migration, messaging-infra-to-metadata, navigation refactor,
message-thread label fix, search-menu-item label).
- The `1-21-upgrade-version-command.module.ts` registration and the
`V1_21_UpgradeVersionCommandModule` import from
`WorkspaceCommandProviderModule`.

**Kept** (intentionally): the 3 `1-21-instance-command-fast-*` files.
Unlike workspace commands (which mutate data), instance commands carry
**schema deltas** still required by current entity definitions
(`AddViewFieldGroupIdIndex`, `MigrateMessagingCalendarToCore`,
`AddEmailThreadWidgetType`). They remain registered in
`INSTANCE_COMMANDS` and `'1.21.0'` stays in `TWENTY_PREVIOUS_VERSIONS`.
They will fold away naturally on a future version bump when a
`CoreMigrationCheck`-style snapshot picks them up.

## Test plan

- [x] `npx nx typecheck twenty-shared`
- [x] `npx nx typecheck twenty-server`
- [x] `npx nx typecheck twenty-front`
- [x] `npx prettier --check` on the changed files
- [x] `npx oxlint` on the changed server files
- [x] `npx jest feature-flag` (4 suites, 20 tests pass)
- [x] `npx jest workspace-entity-manager` (1 suite, 5 tests pass)
2026-05-12 11:25:20 +02:00
b6b4824104 ci(preview-env): drop yarn -- separator so --light reaches the seed command (#20479)
## Summary

Follow-up to #20464. That PR added `--light` to the preview env seed
command but left the `--` between `yarn command:prod` and the script
args. After yarn strips its own `--`, nest-commander still sees `argv:
[..., '--', 'workspace:seed:dev', '--light']`. Commander.js treats `--`
as the end-of-options marker, so `--light` is parsed as a positional arg
and silently ignored — the seed runs in full mode (Apple + YCombinator +
Empty3 + Empty4) and Empty4 still ends up as the default workspace.

## Evidence

In the preview run on `f706cc052b` (which had #20464's `--light` flag),
the seed step took only ~40s but the `GqlTypeGenerator` log emits four
regenerations across two workspaces with custom objects:

- 28 standard → 28 + 5 custom (`rocket, surveyResult, employmentHistory,
petCareAgreement, pet`) — matches Apple
- 28 standard → 28 + 1 custom (`surveyResult`) — matches YCombinator

With `--light` actually applied, `getLightConfig` returns `{ objects:
[], fields: [] }` so no custom objects should be generated.

The working `twenty-app-dev` invocation in
`packages/twenty-docker/twenty-app-dev/rootfs/etc/s6-overlay/scripts/init-db.sh:66`
is `yarn command:prod workspace:seed:dev --light` — no `--`. Matching
that fixes it.

## Test plan

- [ ] Trigger the preview-app label on a PR, confirm only the Apple
workspace is created and `tim@apple.dev` signs in there
- [ ] Confirm the seed step still passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 10:50:08 +02:00
Félix MalfaitandGitHub 8d54ff6ca0 fix(ci): probe real schema in breaking-changes server readiness check (#20465)
## Summary

The `GraphQL and OpenAPI Breaking Changes Detection` workflow has been
posting graphql-inspector stack traces as PR comments — see [#20445
comment](https://github.com/twentyhq/twenty/pull/20445#issuecomment-4421142635)
for an example.

### Root cause

- The wait step probed readiness with `curl -s URL > /dev/null 2>&1`,
which exits 0 for **any** HTTP response — including 5xx and GraphQL
error JSON. NestJS opens the HTTP listener before the workspace schema
cache is fully populated, so the wait often completed while the server
still served auth/metadata error JSON.
- The introspection download therefore wrote a small (~154-byte) error
payload instead of the real schema. `jq empty` in the validation step
only checks JSON *syntax*, so `{"errors":[...]}` passed validation.
- `graphql-inspector diff` then failed with `Unable to read JSON file:
... Not valid JSON content`, the workflow swallowed the error into the
diff markdown, and the bot posted that stack trace verbatim on the PR.

In the failing run, the main-branch files were 154 B (GraphQL) and 112 B
(REST 500); the current-branch files in the same run were 600 KB–2.8 MB.

### Fix

- Wait steps now POST an authenticated introspection (`{ __schema {
queryType { name } } }`) and require `.data.__schema` plus a 2xx
response from `/rest/open-api/core` (`curl -f`) before declaring the
server ready.
- Validation step now checks for the expected shape (`.data.__schema`
for GraphQL, `.openapi`/`.swagger` for OpenAPI) and includes the first
200 bytes of any bad payload in the warning, so when something genuinely
goes wrong the next debugger has a real lead instead of a generic stack
trace.

## Test plan

- [ ] CI runs against this branch — the workflow's own readiness probes
are now exercised against the real server, so a green run validates the
new check.
- [ ] If the readiness probe still passes but downloads regress, the
strengthened validation step will surface the payload in the workflow
logs instead of posting a graphql-inspector stack trace on the PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-12 06:43:35 +00:00
Abdul RahmanandGitHub 1adb59f7f8 Support optional labels on logic-function input schema fields (#20471)
Adds an optional label field to logic-function input schema properties
(InputSchemaProperty and InputJsonSchema). When set, the workflow
builder renders the label in place of the raw property key for both leaf
inputs and nested sections; when unset, it falls back to the key.
jsonSchemaToInputSchema propagates the label so app authors can declare
it in their JSON schema. Payload paths, the variable picker, and saved
workflow inputs continue to use the property key — labels are
display-only.
2026-05-12 06:40:10 +00:00
241 changed files with 1322 additions and 4484 deletions
+38 -12
View File
@@ -164,9 +164,17 @@ jobs:
interval=5
elapsed=0
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
while [ $elapsed -lt $timeout ]; do
if curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \
curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then
GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}')
if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \
curl -fsS "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then
echo "Current branch server is ready!"
break
fi
@@ -177,10 +185,9 @@ jobs:
done
if [ $elapsed -ge $timeout ]; then
echo "Timeout waiting for current branch server to start"
echo "::warning::Timed out waiting for current branch server to serve a valid schema. Validation will skip the API diff."
echo "Current server log:"
cat /tmp/current-server.log || echo "No current server log found"
exit 1
fi
- name: Download GraphQL and REST responses from current branch
@@ -324,9 +331,17 @@ jobs:
interval=5
elapsed=0
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
while [ $elapsed -lt $timeout ]; do
if curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \
curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then
GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}')
if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \
curl -fsS "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then
echo "Main branch server is ready!"
break
fi
@@ -337,10 +352,9 @@ jobs:
done
if [ $elapsed -ge $timeout ]; then
echo "Timeout waiting for main branch server to start"
echo "::warning::Timed out waiting for main branch server to serve a valid schema. Validation will skip the API diff."
echo "Main server log:"
cat /tmp/main-server.log || echo "No main server log found"
exit 1
fi
- name: Download GraphQL and REST responses from main branch
@@ -407,11 +421,23 @@ jobs:
valid=true
for file in main-schema-introspection.json current-schema-introspection.json \
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
main-rest-api.json current-rest-api.json \
main-metadata-schema-introspection.json current-metadata-schema-introspection.json; do
if [ ! -f "$file" ]; then
echo "::warning::Missing GraphQL schema file: $file"
valid=false
elif ! jq -e '.data.__schema' "$file" >/dev/null 2>&1; then
echo "::warning::File $file is not a valid GraphQL introspection result. First 200 bytes: $(head -c 200 "$file")"
valid=false
fi
done
for file in main-rest-api.json current-rest-api.json \
main-rest-metadata-api.json current-rest-metadata-api.json; do
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing schema file: $file"
if [ ! -f "$file" ]; then
echo "::warning::Missing OpenAPI spec file: $file"
valid=false
elif ! jq -e '.openapi // .swagger' "$file" >/dev/null 2>&1; then
echo "::warning::File $file is not a valid OpenAPI spec. First 200 bytes: $(head -c 200 "$file")"
valid=false
fi
done
+1 -1
View File
@@ -144,7 +144,7 @@ jobs:
run: |
cd packages/twenty-docker/
echo "Seeding light dev workspace (Apple only)..."
if ! docker compose exec -T server yarn command:prod -- workspace:seed:dev --light; then
if ! docker compose exec -T server yarn command:prod workspace:seed:dev --light; then
echo "❌ Seeding light dev workspace failed. Dumping server logs..."
docker compose logs server
exit 1
@@ -1,4 +1,4 @@
import { PermissionFlagGrant, defineRole } from 'twenty-sdk/define';
import { PermissionFlag, defineRole } from 'twenty-sdk/define';
import {
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
POST_CARD_UNIVERSAL_IDENTIFIER,
@@ -36,5 +36,5 @@ export default defineRole({
canUpdateFieldValue: true,
},
],
permissionFlagGrants: [PermissionFlagGrant.APPLICATIONS],
permissionFlags: [PermissionFlag.APPLICATIONS],
});
@@ -1,4 +1,4 @@
import { PermissionFlagGrant, defineRole } from 'twenty-sdk/define';
import { PermissionFlag, defineRole } from 'twenty-sdk/define';
import {
CONTENT_FIELD_UNIVERSAL_IDENTIFIER,
POST_CARD_UNIVERSAL_IDENTIFIER,
@@ -36,5 +36,5 @@ export default defineRole({
canUpdateFieldValue: false,
},
],
permissionFlagGrants: [PermissionFlagGrant.APPLICATIONS],
permissionFlags: [PermissionFlag.APPLICATIONS],
});
@@ -12,5 +12,5 @@ export default defineRole({
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: false,
permissionFlagGrants: [PermissionFlagType.UPLOAD_FILE, PermissionFlagType.AI],
permissionFlags: [PermissionFlagType.UPLOAD_FILE, PermissionFlagType.AI],
});
@@ -21,5 +21,5 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagGrants: [],
permissionFlags: [],
});
@@ -18,5 +18,5 @@ export default defineRole({
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagGrants: [],
permissionFlags: [],
});
@@ -138,7 +138,7 @@ type UserWorkspace {
createdAt: DateTime!
updatedAt: DateTime!
deletedAt: DateTime
permissionFlagGrants: [PermissionFlagType!]
permissionFlags: [PermissionFlagType!]
objectPermissions: [ObjectPermission!]
objectsPermissions: [ObjectPermission!]
twoFactorAuthenticationMethodSummary: [TwoFactorAuthenticationMethodSummary!]
@@ -244,7 +244,7 @@ type FieldPermission {
canUpdateFieldValue: Boolean
}
type PermissionFlagGrant {
type PermissionFlag {
id: UUID!
roleId: UUID!
flag: PermissionFlagType!
@@ -276,7 +276,7 @@ type Role {
canUpdateAllObjectRecords: Boolean!
canSoftDeleteAllObjectRecords: Boolean!
canDestroyAllObjectRecords: Boolean!
permissionFlagGrants: [PermissionFlagGrant!]
permissionFlags: [PermissionFlag!]
objectPermissions: [ObjectPermission!]
fieldPermissions: [FieldPermission!]
rowLevelPermissionPredicates: [RowLevelPermissionPredicate!]
@@ -1748,19 +1748,14 @@ type FeatureFlag {
enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED
IS_JSON_FILTER_ENABLED
IS_COMMAND_MENU_ITEM_ENABLED
IS_MARKETPLACE_SETTING_TAB_VISIBLE
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_EMAIL_GROUP_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
IS_CONNECTED_ACCOUNT_MIGRATED
IS_RICH_TEXT_V1_MIGRATED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_DATASOURCE_MIGRATED
IS_BILLING_V2_ENABLED
IS_CUSTOM_PERMISSION_FLAGS_ENABLED
}
type WorkspaceUrls {
@@ -2856,23 +2851,6 @@ enum MessageFolderPendingSyncAction {
NONE
}
type PermissionFlag {
id: UUID!
universalIdentifier: UUID!
key: String!
label: String!
description: String
iconKey: String
permissionType: String!
isRelevantForAgents: Boolean!
isRelevantForUsers: Boolean!
isRelevantForApiKeys: Boolean!
isCustom: Boolean!
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
}
type CollectionHash {
collectionName: AllMetadataName!
hash: String!
@@ -2901,7 +2879,6 @@ enum AllMetadataName {
pageLayoutTab
commandMenuItem
navigationMenuItem
permissionFlagGrant
permissionFlag
objectPermission
fieldPermission
@@ -3039,8 +3016,6 @@ type Query {
myCalendarChannels(connectedAccountId: UUID): [CalendarChannel!]!
webhooks: [Webhook!]!
webhook(id: UUID!): Webhook
permissionFlags: [PermissionFlag!]!
permissionFlag(id: UUID!): PermissionFlag
minimalMetadata: MinimalMetadata!
chatThreads: [AgentChatThread!]!
chatThread(id: UUID!): AgentChatThread!
@@ -3258,7 +3233,7 @@ type Mutation {
updateOneRole(updateRoleInput: UpdateRoleInput!): Role!
deleteOneRole(roleId: UUID!): String!
upsertObjectPermissions(upsertObjectPermissionsInput: UpsertObjectPermissionsInput!): [ObjectPermission!]!
upsertPermissionFlagGrants(upsertPermissionFlagGrantsInput: UpsertPermissionFlagGrantsInput!): [PermissionFlagGrant!]!
upsertPermissionFlags(upsertPermissionFlagsInput: UpsertPermissionFlagsInput!): [PermissionFlag!]!
upsertFieldPermissions(upsertFieldPermissionsInput: UpsertFieldPermissionsInput!): [FieldPermission!]!
upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult!
assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean!
@@ -3282,9 +3257,6 @@ type Mutation {
createWebhook(input: CreateWebhookInput!): Webhook!
updateWebhook(input: UpdateWebhookInput!): Webhook!
deleteWebhook(id: UUID!): Webhook!
createPermissionFlag(input: CreatePermissionFlagInput!): PermissionFlag!
updatePermissionFlag(input: UpdatePermissionFlagInput!): PermissionFlag!
deletePermissionFlag(id: UUID!): PermissionFlag!
createChatThread: AgentChatThread!
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String, fileIds: [UUID!]): SendChatMessageResult!
stopAgentChatStream(threadId: UUID!): Boolean!
@@ -4044,9 +4016,9 @@ input ObjectPermissionInput {
canDestroyObjectRecords: Boolean
}
input UpsertPermissionFlagGrantsInput {
input UpsertPermissionFlagsInput {
roleId: UUID!
permissionFlagGrantKeys: [PermissionFlagType!]!
permissionFlagKeys: [PermissionFlagType!]!
}
input UpsertFieldPermissionsInput {
@@ -4248,37 +4220,6 @@ input UpdateWebhookInputUpdates {
secret: String
}
input CreatePermissionFlagInput {
id: UUID
universalIdentifier: UUID
key: String!
label: String!
description: String
iconKey: String
permissionType: String!
isRelevantForAgents: Boolean
isRelevantForUsers: Boolean
isRelevantForApiKeys: Boolean
}
input UpdatePermissionFlagInput {
"""The id of the permission flag definition to update"""
id: UUID!
"""The fields to update"""
update: UpdatePermissionFlagInputUpdates!
}
input UpdatePermissionFlagInputUpdates {
label: String
description: String
iconKey: String
permissionType: String
isRelevantForAgents: Boolean
isRelevantForUsers: Boolean
isRelevantForApiKeys: Boolean
}
input CreateSkillInput {
id: UUID
name: String!
@@ -117,7 +117,7 @@ export interface UserWorkspace {
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
deletedAt?: Scalars['DateTime']
permissionFlagGrants?: PermissionFlagType[]
permissionFlags?: PermissionFlagType[]
objectPermissions?: ObjectPermission[]
objectsPermissions?: ObjectPermission[]
twoFactorAuthenticationMethodSummary?: TwoFactorAuthenticationMethodSummary[]
@@ -190,11 +190,11 @@ export interface FieldPermission {
__typename: 'FieldPermission'
}
export interface PermissionFlagGrant {
export interface PermissionFlag {
id: Scalars['UUID']
roleId: Scalars['UUID']
flag: PermissionFlagType
__typename: 'PermissionFlagGrant'
__typename: 'PermissionFlag'
}
export interface ApiKeyForRole {
@@ -224,7 +224,7 @@ export interface Role {
canUpdateAllObjectRecords: Scalars['Boolean']
canSoftDeleteAllObjectRecords: Scalars['Boolean']
canDestroyAllObjectRecords: Scalars['Boolean']
permissionFlagGrants?: PermissionFlagGrant[]
permissionFlags?: PermissionFlag[]
objectPermissions?: ObjectPermission[]
fieldPermissions?: FieldPermission[]
rowLevelPermissionPredicates?: RowLevelPermissionPredicate[]
@@ -1389,7 +1389,7 @@ export interface FeatureFlag {
__typename: 'FeatureFlag'
}
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED' | 'IS_BILLING_V2_ENABLED' | 'IS_CUSTOM_PERMISSION_FLAGS_ENABLED'
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_BILLING_V2_ENABLED'
export interface WorkspaceUrls {
customUrl?: Scalars['String']
@@ -2502,31 +2502,13 @@ export interface MessageFolder {
export type MessageFolderPendingSyncAction = 'FOLDER_DELETION' | 'NONE'
export interface PermissionFlag {
id: Scalars['UUID']
universalIdentifier: Scalars['UUID']
key: Scalars['String']
label: Scalars['String']
description?: Scalars['String']
iconKey?: Scalars['String']
permissionType: Scalars['String']
isRelevantForAgents: Scalars['Boolean']
isRelevantForUsers: Scalars['Boolean']
isRelevantForApiKeys: Scalars['Boolean']
isCustom: Scalars['Boolean']
applicationId: Scalars['UUID']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
__typename: 'PermissionFlag'
}
export interface CollectionHash {
collectionName: AllMetadataName
hash: Scalars['String']
__typename: 'CollectionHash'
}
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlagGrant' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider'
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider'
export interface MinimalObjectMetadata {
id: Scalars['UUID']
@@ -2633,8 +2615,6 @@ export interface Query {
myCalendarChannels: CalendarChannel[]
webhooks: Webhook[]
webhook?: Webhook
permissionFlags: PermissionFlag[]
permissionFlag?: PermissionFlag
minimalMetadata: MinimalMetadata
chatThreads: AgentChatThread[]
chatThread: AgentChatThread
@@ -2785,7 +2765,7 @@ export interface Mutation {
updateOneRole: Role
deleteOneRole: Scalars['String']
upsertObjectPermissions: ObjectPermission[]
upsertPermissionFlagGrants: PermissionFlagGrant[]
upsertPermissionFlags: PermissionFlag[]
upsertFieldPermissions: FieldPermission[]
upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult
assignRoleToAgent: Scalars['Boolean']
@@ -2809,9 +2789,6 @@ export interface Mutation {
createWebhook: Webhook
updateWebhook: Webhook
deleteWebhook: Webhook
createPermissionFlag: PermissionFlag
updatePermissionFlag: PermissionFlag
deletePermissionFlag: PermissionFlag
createChatThread: AgentChatThread
sendChatMessage: SendChatMessageResult
stopAgentChatStream: Scalars['Boolean']
@@ -3026,7 +3003,7 @@ export interface UserWorkspaceGenqlSelection{
createdAt?: boolean | number
updatedAt?: boolean | number
deletedAt?: boolean | number
permissionFlagGrants?: boolean | number
permissionFlags?: boolean | number
objectPermissions?: ObjectPermissionGenqlSelection
objectsPermissions?: ObjectPermissionGenqlSelection
twoFactorAuthenticationMethodSummary?: TwoFactorAuthenticationMethodSummaryGenqlSelection
@@ -3090,7 +3067,7 @@ export interface FieldPermissionGenqlSelection{
__scalar?: boolean | number
}
export interface PermissionFlagGrantGenqlSelection{
export interface PermissionFlagGenqlSelection{
id?: boolean | number
roleId?: boolean | number
flag?: boolean | number
@@ -3126,7 +3103,7 @@ export interface RoleGenqlSelection{
canUpdateAllObjectRecords?: boolean | number
canSoftDeleteAllObjectRecords?: boolean | number
canDestroyAllObjectRecords?: boolean | number
permissionFlagGrants?: PermissionFlagGrantGenqlSelection
permissionFlags?: PermissionFlagGenqlSelection
objectPermissions?: ObjectPermissionGenqlSelection
fieldPermissions?: FieldPermissionGenqlSelection
rowLevelPermissionPredicates?: RowLevelPermissionPredicateGenqlSelection
@@ -5553,25 +5530,6 @@ export interface MessageFolderGenqlSelection{
__scalar?: boolean | number
}
export interface PermissionFlagGenqlSelection{
id?: boolean | number
universalIdentifier?: boolean | number
key?: boolean | number
label?: boolean | number
description?: boolean | number
iconKey?: boolean | number
permissionType?: boolean | number
isRelevantForAgents?: boolean | number
isRelevantForUsers?: boolean | number
isRelevantForApiKeys?: boolean | number
isCustom?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface CollectionHashGenqlSelection{
collectionName?: boolean | number
hash?: boolean | number
@@ -5706,8 +5664,6 @@ export interface QueryGenqlSelection{
myCalendarChannels?: (CalendarChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} })
webhooks?: WebhookGenqlSelection
webhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} })
permissionFlags?: PermissionFlagGenqlSelection
permissionFlag?: (PermissionFlagGenqlSelection & { __args: {id: Scalars['UUID']} })
minimalMetadata?: MinimalMetadataGenqlSelection
chatThreads?: AgentChatThreadGenqlSelection
chatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID']} })
@@ -5879,7 +5835,7 @@ export interface MutationGenqlSelection{
updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} })
deleteOneRole?: { __args: {roleId: Scalars['UUID']} }
upsertObjectPermissions?: (ObjectPermissionGenqlSelection & { __args: {upsertObjectPermissionsInput: UpsertObjectPermissionsInput} })
upsertPermissionFlagGrants?: (PermissionFlagGrantGenqlSelection & { __args: {upsertPermissionFlagGrantsInput: UpsertPermissionFlagGrantsInput} })
upsertPermissionFlags?: (PermissionFlagGenqlSelection & { __args: {upsertPermissionFlagsInput: UpsertPermissionFlagsInput} })
upsertFieldPermissions?: (FieldPermissionGenqlSelection & { __args: {upsertFieldPermissionsInput: UpsertFieldPermissionsInput} })
upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} })
assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} }
@@ -5903,9 +5859,6 @@ export interface MutationGenqlSelection{
createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} })
updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} })
deleteWebhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} })
createPermissionFlag?: (PermissionFlagGenqlSelection & { __args: {input: CreatePermissionFlagInput} })
updatePermissionFlag?: (PermissionFlagGenqlSelection & { __args: {input: UpdatePermissionFlagInput} })
deletePermissionFlag?: (PermissionFlagGenqlSelection & { __args: {id: Scalars['UUID']} })
createChatThread?: AgentChatThreadGenqlSelection
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null), fileIds?: (Scalars['UUID'][] | null)} })
stopAgentChatStream?: { __args: {threadId: Scalars['UUID']} }
@@ -6222,7 +6175,7 @@ export interface UpsertObjectPermissionsInput {roleId: Scalars['UUID'],objectPer
export interface ObjectPermissionInput {objectMetadataId: Scalars['UUID'],canReadObjectRecords?: (Scalars['Boolean'] | null),canUpdateObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteObjectRecords?: (Scalars['Boolean'] | null),canDestroyObjectRecords?: (Scalars['Boolean'] | null)}
export interface UpsertPermissionFlagGrantsInput {roleId: Scalars['UUID'],permissionFlagGrantKeys: PermissionFlagType[]}
export interface UpsertPermissionFlagsInput {roleId: Scalars['UUID'],permissionFlagKeys: PermissionFlagType[]}
export interface UpsertFieldPermissionsInput {roleId: Scalars['UUID'],fieldPermissions: FieldPermissionInput[]}
@@ -6296,16 +6249,6 @@ update: UpdateWebhookInputUpdates}
export interface UpdateWebhookInputUpdates {targetUrl?: (Scalars['String'] | null),operations?: (Scalars['String'][] | null),description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)}
export interface CreatePermissionFlagInput {id?: (Scalars['UUID'] | null),universalIdentifier?: (Scalars['UUID'] | null),key: Scalars['String'],label: Scalars['String'],description?: (Scalars['String'] | null),iconKey?: (Scalars['String'] | null),permissionType: Scalars['String'],isRelevantForAgents?: (Scalars['Boolean'] | null),isRelevantForUsers?: (Scalars['Boolean'] | null),isRelevantForApiKeys?: (Scalars['Boolean'] | null)}
export interface UpdatePermissionFlagInput {
/** The id of the permission flag definition to update */
id: Scalars['UUID'],
/** The fields to update */
update: UpdatePermissionFlagInputUpdates}
export interface UpdatePermissionFlagInputUpdates {label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),iconKey?: (Scalars['String'] | null),permissionType?: (Scalars['String'] | null),isRelevantForAgents?: (Scalars['Boolean'] | null),isRelevantForUsers?: (Scalars['Boolean'] | null),isRelevantForApiKeys?: (Scalars['Boolean'] | null)}
export interface CreateSkillInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],label: Scalars['String'],icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content: Scalars['String']}
export interface UpdateSkillInput {id: Scalars['UUID'],name?: (Scalars['String'] | null),label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content?: (Scalars['String'] | null),isActive?: (Scalars['Boolean'] | null)}
@@ -6473,10 +6416,10 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const PermissionFlagGrant_possibleTypes: string[] = ['PermissionFlagGrant']
export const isPermissionFlagGrant = (obj?: { __typename?: any } | null): obj is PermissionFlagGrant => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPermissionFlagGrant"')
return PermissionFlagGrant_possibleTypes.includes(obj.__typename)
const PermissionFlag_possibleTypes: string[] = ['PermissionFlag']
export const isPermissionFlag = (obj?: { __typename?: any } | null): obj is PermissionFlag => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPermissionFlag"')
return PermissionFlag_possibleTypes.includes(obj.__typename)
}
@@ -8265,14 +8208,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const PermissionFlag_possibleTypes: string[] = ['PermissionFlag']
export const isPermissionFlag = (obj?: { __typename?: any } | null): obj is PermissionFlag => {
if (!obj?.__typename) throw new Error('__typename is missing in "isPermissionFlag"')
return PermissionFlag_possibleTypes.includes(obj.__typename)
}
const CollectionHash_possibleTypes: string[] = ['CollectionHash']
export const isCollectionHash = (obj?: { __typename?: any } | null): obj is CollectionHash => {
if (!obj?.__typename) throw new Error('__typename is missing in "isCollectionHash"')
@@ -8788,19 +8723,14 @@ export const enumLogicFunctionExecutionStatus = {
export const enumFeatureFlagKey = {
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const,
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const,
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const,
IS_CUSTOM_PERMISSION_FLAGS_ENABLED: 'IS_CUSTOM_PERMISSION_FLAGS_ENABLED' as const
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const
}
export const enumIdentityProviderType = {
@@ -8962,7 +8892,6 @@ export const enumAllMetadataName = {
pageLayoutTab: 'pageLayoutTab' as const,
commandMenuItem: 'commandMenuItem' as const,
navigationMenuItem: 'navigationMenuItem' as const,
permissionFlagGrant: 'permissionFlagGrant' as const,
permissionFlag: 'permissionFlag' as const,
objectPermission: 'objectPermission' as const,
fieldPermission: 'fieldPermission' as const,
File diff suppressed because it is too large Load Diff
@@ -9,7 +9,7 @@ A **role** is a permission set: which objects an app can read or write, which fi
```ts src/roles/restricted-company-role.ts
import {
defineRole,
PermissionFlagGrant,
PermissionFlag,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
@@ -45,7 +45,7 @@ export default defineRole({
canUpdateFieldValue: false,
},
],
permissionFlagGrants: [PermissionFlagGrant.APPLICATIONS],
permissionFlags: [PermissionFlag.APPLICATIONS],
});
```
@@ -54,7 +54,7 @@ export default defineRole({
When you scaffold a new app, the CLI creates a default role file declared with `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineApplicationRole, PermissionFlagGrant } from 'twenty-sdk/define';
import { defineApplicationRole, PermissionFlag } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
@@ -73,7 +73,7 @@ export default defineApplicationRole({
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagGrants: [],
permissionFlags: [],
});
```
@@ -88,5 +88,5 @@ Notes:
- Start from the scaffolded role, then progressively restrict it — the default grants broad read access, which is rarely what you want in production.
- Replace `objectPermissions` and `fieldPermissions` with the exact objects and fields your functions actually need.
- `permissionFlagGrants` control access to platform-level capabilities. Keep them minimal.
- `permissionFlags` control access to platform-level capabilities. Keep them minimal.
- See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
@@ -57,7 +57,7 @@ const GET_ROLES_QUERY = `
expiresAt
revokedAt
}
permissionFlagGrants {
permissionFlags {
__typename
id
flag
@@ -271,10 +271,6 @@ export type FeatureFlag = {
export enum FeatureFlagKey {
IS_BILLING_V2_ENABLED = 'IS_BILLING_V2_ENABLED',
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED',
IS_CUSTOM_PERMISSION_FLAGS_ENABLED = 'IS_CUSTOM_PERMISSION_FLAGS_ENABLED',
IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED',
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
IS_EMAIL_GROUP_ENABLED = 'IS_EMAIL_GROUP_ENABLED',
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
@@ -283,7 +279,6 @@ export enum FeatureFlagKey {
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@ import {
filterAttachmentsWithFile,
} from '@/activities/files/utils/filterAttachmentsWithFile';
import { getAttachmentUrl } from '@/activities/utils/getAttachmentUrl';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { isDefined } from 'twenty-shared/utils';
@@ -129,11 +129,11 @@ export const AttachmentList = ({
isAttachmentPreviewEnabledState,
);
const hasDownloadPermission = useHasPermissionFlagGrant(
const hasDownloadPermission = useHasPermissionFlag(
PermissionFlagType.DOWNLOAD_FILE,
);
const hasUploadPermission = useHasPermissionFlagGrant(
const hasUploadPermission = useHasPermissionFlag(
PermissionFlagType.UPLOAD_FILE,
);
@@ -15,7 +15,7 @@ import { getSafeUrl, isDefined } from 'twenty-shared/utils';
import { type AttachmentWithFile } from '@/activities/files/utils/filterAttachmentsWithFile';
import { FileIcon } from '@/file/components/FileIcon';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
@@ -83,7 +83,7 @@ export const AttachmentRow = ({
const { theme } = useContext(ThemeContext);
const [isEditing, setIsEditing] = useState(false);
const hasDownloadPermission = useHasPermissionFlagGrant(
const hasDownloadPermission = useHasPermissionFlag(
PermissionFlagType.DOWNLOAD_FILE,
);
@@ -8,7 +8,7 @@ import { useAttachments } from '@/activities/files/hooks/useAttachments';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { Trans, useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
@@ -82,7 +82,7 @@ export const FilesCard = () => {
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
const hasUploadPermission = useHasPermissionFlagGrant(
const hasUploadPermission = useHasPermissionFlag(
PermissionFlagType.UPLOAD_FILE,
);
@@ -4,7 +4,7 @@ import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod';
import { useGetNextResourceCreditPrice } from '@/settings/billing/hooks/useGetNextResourceCreditPrice';
import { useGetNextMeteredBillingPrice } from '@/settings/billing/hooks/useGetNextMeteredBillingPrice';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@@ -52,7 +52,7 @@ export const AIChatNoMoreBillingCreditsBanner = () => {
);
const { [PermissionFlagType.WORKSPACE]: hasPermissionToManageBilling } =
usePermissionFlagGrantMap();
usePermissionFlagMap();
if (!hasPermissionToManageBilling) {
return null;
@@ -17,7 +17,7 @@ import { hasTriggeredCreateForDraftState } from '@/ai/states/hasTriggeredCreateF
import { sortChatThreadsByLastActivityDesc } from '@/ai/utils/sortChatThreadsByLastActivityDesc';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useAtomComponentFamilyStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateCallbackState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
@@ -31,7 +31,7 @@ import {
export const AgentChatThreadInitializationEffect = () => {
const client = useApolloClient();
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
const hasAiSettingsPermission = useHasPermissionFlagGrant(
const hasAiSettingsPermission = useHasPermissionFlag(
PermissionFlagType.AI_SETTINGS,
);
@@ -1,5 +1,5 @@
import { AiChatBanner } from '@/ai/components/AiChatBanner';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -17,7 +17,7 @@ export const AiChatCreditsExhaustedMessage = () => {
const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing;
const { [PermissionFlagType.WORKSPACE]: hasPermissionToManageBilling } =
usePermissionFlagGrantMap();
usePermissionFlagMap();
const handleUpgradeClick = () => {
navigateSettings(SettingsPath.Billing);
@@ -4,7 +4,7 @@ import { type UserWorkspace } from '~/generated-metadata/graphql';
export type CurrentUserWorkspace = Pick<
UserWorkspace,
'permissionFlagGrants' | 'twoFactorAuthenticationMethodSummary'
'permissionFlags' | 'twoFactorAuthenticationMethodSummary'
> & {
objectsPermissions: Array<ObjectPermissions & { objectMetadataId: string }>;
};
@@ -40,10 +40,10 @@ export const StandalonePageCommandMenu = () => {
featureFlags[flag.key] = flag.value === true;
}
const permissionFlagGrants: Record<string, boolean> = {};
const permissionFlags: Record<string, boolean> = {};
for (const flag of currentUserWorkspace?.permissionFlagGrants ?? []) {
permissionFlagGrants[flag] = true;
for (const flag of currentUserWorkspace?.permissionFlags ?? []) {
permissionFlags[flag] = true;
}
const targetObjectReadPermissions: Record<string, boolean> = {};
@@ -82,7 +82,7 @@ export const StandalonePageCommandMenu = () => {
},
selectedRecords: [],
featureFlags,
permissionFlagGrants,
permissionFlags,
targetObjectReadPermissions,
targetObjectWritePermissions,
objectMetadataItem: {},
@@ -90,7 +90,7 @@ export const StandalonePageCommandMenu = () => {
};
}, [
currentWorkspace?.featureFlags,
currentUserWorkspace?.permissionFlagGrants,
currentUserWorkspace?.permissionFlags,
isLayoutCustomizationModeEnabled,
objectMetadataItems,
store,
@@ -24,7 +24,7 @@ export const EMPTY_COMMAND_MENU_CONTEXT_API: CommandMenuContextApi = {
},
selectedRecords: [],
featureFlags: {},
permissionFlagGrants: {},
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
objectMetadataItem: {},
@@ -64,7 +64,7 @@ const getWrapper =
},
selectedRecords: [],
featureFlags: {},
permissionFlagGrants: {},
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
objectMetadataItem: {},
@@ -145,10 +145,10 @@ export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
const permissionFlagGrants: Record<string, boolean> = {};
const permissionFlags: Record<string, boolean> = {};
for (const flag of currentUserWorkspace?.permissionFlagGrants ?? []) {
permissionFlagGrants[flag] = true;
for (const flag of currentUserWorkspace?.permissionFlags ?? []) {
permissionFlags[flag] = true;
}
const targetObjectReadPermissions: Record<string, boolean> = {};
@@ -185,7 +185,7 @@ export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
objectPermissions,
selectedRecords,
featureFlags,
permissionFlagGrants,
permissionFlags,
targetObjectReadPermissions,
targetObjectWritePermissions,
objectMetadataItem: objectMetadataItem ?? {},
@@ -10,7 +10,7 @@ import { InformationBannerInvalidEnterpriseKey } from '@/information-banner/comp
import { InformationBannerMaintenance } from '@/information-banner/components/maintenance/InformationBannerMaintenance';
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
@@ -32,7 +32,7 @@ const StyledInformationBannerWrapper = styled.div`
export const InformationBannerWrapper = () => {
const subscriptionStatus = useSubscriptionStatus();
const permissionMap = usePermissionFlagGrantMap();
const permissionMap = usePermissionFlagMap();
const isAccountSyncEnabled =
permissionMap[PermissionFlagType.CONNECTED_ACCOUNTS];
const isWorkspaceSuspended = useIsWorkspaceActivationStatusEqualsTo(
@@ -1,7 +1,7 @@
import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -24,7 +24,7 @@ export const InformationBannerBillingSubscriptionPaused = () => {
const {
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = usePermissionFlagGrantMap();
} = usePermissionFlagMap();
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
@@ -1,6 +1,6 @@
import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useLingui } from '@lingui/react/macro';
import { PermissionFlagType } from '~/generated-metadata/graphql';
@@ -9,7 +9,7 @@ export const InformationBannerEndTrialPeriod = () => {
const { t } = useLingui();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
usePermissionFlagGrantMap();
usePermissionFlagMap();
return (
<InformationBanner
@@ -1,6 +1,6 @@
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -21,7 +21,7 @@ export const InformationBannerFailPaymentInfo = () => {
const {
[PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails,
} = usePermissionFlagGrantMap();
} = usePermissionFlagMap();
const openBillingPortal = () => {
if (isDefined(data) && isDefined(data.billingPortalSession.url)) {
@@ -1,7 +1,7 @@
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/settings/billing/constants/BillingCheckoutSessionDefaultValue';
import { useHandleCheckoutSession } from '@/settings/billing/hooks/useHandleCheckoutSession';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
@@ -16,7 +16,7 @@ export const InformationBannerNoBillingSubscription = () => {
});
const { [PermissionFlagType.WORKSPACE]: hasPermissionToSubscribe } =
usePermissionFlagGrantMap();
usePermissionFlagMap();
return (
<InformationBanner
@@ -1,5 +1,5 @@
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
import { PermissionFlagType } from '~/generated-metadata/graphql';
@@ -9,7 +9,7 @@ export const InformationBannerNoMoreCredits = () => {
const { t } = useLingui();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateCreditPlan } =
usePermissionFlagGrantMap();
usePermissionFlagMap();
const navigateSettings = useNavigateSettings();
@@ -30,7 +30,6 @@ export const useMetadataErrorHandler = () => {
viewFilter: t`view filter`,
index: t`index`,
logicFunction: t`logic function`,
permissionFlagGrant: t`permission flag grant`,
permissionFlag: t`permission flag`,
objectPermission: t`object permission`,
fieldPermission: t`field permission`,
@@ -104,9 +104,8 @@ export const UserMetadataProviderInitialEffect = () => {
if (isDefined(userQueryData.currentUser.currentUserWorkspace)) {
setCurrentUserWorkspace({
permissionFlagGrants:
userQueryData.currentUser.currentUserWorkspace.permissionFlagGrants ??
[],
permissionFlags:
userQueryData.currentUser.currentUserWorkspace.permissionFlags ?? [],
twoFactorAuthenticationMethodSummary:
userQueryData.currentUser.currentUserWorkspace
.twoFactorAuthenticationMethodSummary ?? [],
@@ -7,7 +7,7 @@ export type FlatRole = Omit<
| 'workspaceMembers'
| 'fieldPermissions'
| 'objectPermissions'
| 'permissionFlagGrants'
| 'permissionFlags'
| 'rowLevelPermissionPredicateGroups'
| 'rowLevelPermissionPredicates'
>;
@@ -2,7 +2,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { NavigationDrawerAiChatContent } from '@/ai/components/NavigationDrawerAiChatContent';
import { MainNavigationDrawerNavigationContent } from '@/navigation/components/MainNavigationDrawerNavigationContent';
import { MainNavigationDrawerTabsRow } from '@/navigation/components/MainNavigationDrawerTabsRow';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { NavigationDrawer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawer';
import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent';
import { NavigationDrawerScrollableContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerScrollableContent';
@@ -16,7 +16,7 @@ export const MainNavigationDrawer = ({ className }: { className?: string }) => {
navigationDrawerActiveTabState,
);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const hasAiSettingsPermission = useHasPermissionFlagGrant(
const hasAiSettingsPermission = useHasPermissionFlag(
PermissionFlagType.AI_SETTINGS,
);
@@ -12,7 +12,7 @@ import { useIsMobile } from 'twenty-ui/utilities';
import { useContext } from 'react';
import { useSwitchToNewAiChat } from '@/ai/hooks/useSwitchToNewAiChat';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerActiveTabState } from '@/ui/navigation/states/navigationDrawerActiveTabState';
@@ -144,7 +144,7 @@ export const MainNavigationDrawerTabsRow = () => {
const setIsNavigationDrawerExpanded = useSetAtomState(
isNavigationDrawerExpandedState,
);
const hasAiSettingsPermission = useHasPermissionFlagGrant(
const hasAiSettingsPermission = useHasPermissionFlag(
PermissionFlagType.AI_SETTINGS,
);
@@ -5,7 +5,7 @@ import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePat
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useOpenRecordsSearchPageInSidePanel } from '@/side-panel/hooks/useOpenRecordsSearchPageInSidePanel';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
@@ -39,7 +39,7 @@ export const MobileNavigationBar = () => {
const { switchToNewChat } = useSwitchToNewAiChat();
const { alphaSortedActiveNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const hasAiSettingsPermission = useHasPermissionFlagGrant(
const hasAiSettingsPermission = useHasPermissionFlag(
PermissionFlagType.AI_SETTINGS,
);
@@ -151,7 +151,7 @@ export const responseData = {
},
workspaceMembers: [],
currentUserWorkspace: {
permissionFlagGrants: ['DATA_MODEL'],
permissionFlags: ['DATA_MODEL'],
},
currentWorkspace: {
id: 'test-workspace-id',
@@ -105,3 +105,5 @@ exports[`getRecordFromRecordNode should handle null and undefined values 1`] = `
"phone": undefined,
}
`;
exports[`getRecordFromRecordNode should return an empty object when record node is undefined 1`] = `{}`;
@@ -137,4 +137,12 @@ describe('getRecordFromRecordNode', () => {
expect(result).toMatchSnapshot();
});
it('should return an empty object when record node is undefined', () => {
const result = getRecordFromRecordNode({
recordNode: undefined,
});
expect(result).toMatchSnapshot();
});
});
@@ -7,8 +7,12 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const getRecordFromRecordNode = <T extends ObjectRecord>({
recordNode,
}: {
recordNode: RecordGqlNode;
recordNode: RecordGqlNode | undefined;
}): T => {
if (!isDefined(recordNode)) {
return {} as T;
}
const { id, __typename } = recordNode;
return {
@@ -1,13 +1,31 @@
import { captureMessage, withScope } from '@sentry/react';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { type RecordGqlConnectionEdgesRequired } from '@/object-record/graphql/types/RecordGqlConnectionEdgesRequired';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from 'twenty-shared/utils';
export const getRecordsFromRecordConnection = <T extends ObjectRecord>({
recordConnection,
}: {
recordConnection: RecordGqlConnectionEdgesRequired;
}): T[] => {
return recordConnection?.edges?.map((edge) =>
getRecordFromRecordNode<T>({ recordNode: edge.node }),
);
const edges = recordConnection?.edges ?? [];
const invalidEdgeCount = edges.filter((edge) => !isDefined(edge?.node)).length;
if (invalidEdgeCount > 0) {
withScope((scope) => {
scope.setFingerprint(['record-connection-missing-node']);
scope.setLevel('warning');
scope.setExtra('invalidEdgeCount', invalidEdgeCount);
scope.setExtra('edgesCount', edges.length);
captureMessage('Record connection contains edge without node');
});
}
return edges
.filter((edge) => isDefined(edge?.node))
.map((edge) => getRecordFromRecordNode<T>({ recordNode: edge.node }));
};
@@ -1,6 +1,6 @@
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
@@ -36,9 +36,7 @@ export const ObjectOptionsDropdownVisibilityContent = () => {
const { currentView } = useGetCurrentViewOnly();
const { updateCurrentView } = useUpdateCurrentView();
const { copyToClipboard } = useCopyToClipboard();
const hasViewsPermission = useHasPermissionFlagGrant(
PermissionFlagType.VIEWS,
);
const hasViewsPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
const { canPersistChanges } = useCanPersistViewChanges();
const selectedItemId = useAtomComponentStateValue(
@@ -1,4 +1,4 @@
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { isNonEmptyString } from '@sniptt/guards';
import { useParams } from 'react-router-dom';
import { PermissionFlagType } from '~/generated-metadata/graphql';
@@ -6,7 +6,7 @@ import { PermissionFlagType } from '~/generated-metadata/graphql';
export const useCanAddSelectOption = (fieldName: string) => {
const { objectNamePlural } = useParams();
const userHasPermissionToEditDataModel = useHasPermissionFlagGrant(
const userHasPermissionToEditDataModel = useHasPermissionFlag(
PermissionFlagType.DATA_MODEL,
);
@@ -7,7 +7,7 @@ import { type RecordGroupAction } from '@/object-record/record-group/types/Recor
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorValue';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
@@ -90,7 +90,7 @@ export const useRecordGroupActions = ({
recordIndexGroupFieldMetadataItem,
]);
const hasAccessToDataModelSettings = useHasPermissionFlagGrant(
const hasAccessToDataModelSettings = useHasPermissionFlag(
PermissionFlagType.DATA_MODEL,
);
const currentIndex = visibleRecordGroupIds.findIndex(
@@ -31,8 +31,7 @@ const renderHooks = (
onboardingStatus: OnboardingStatus,
withCurrentBillingSubscription: boolean,
withOneWorkspaceMember = true,
permissionFlagGrants = mockedUserData.currentUserWorkspace
.permissionFlagGrants,
permissionFlags = mockedUserData.currentUserWorkspace.permissionFlags,
) => {
const { result } = renderHook(
() => {
@@ -58,7 +57,7 @@ const renderHooks = (
result.current.setCurrentUser({ ...mockedUserData, onboardingStatus });
result.current.setCurrentUserWorkspace({
...mockedUserData.currentUserWorkspace,
permissionFlagGrants,
permissionFlags,
});
result.current.setCurrentWorkspace({
...mockCurrentWorkspace,
@@ -9,7 +9,7 @@ import {
currentWorkspaceState,
} from '@/auth/states/currentWorkspaceState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useCallback } from 'react';
@@ -67,7 +67,7 @@ export const useSetNextOnboardingStatus = () => {
const currentUser = useAtomStateValue(currentUserState);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const calendarBookingPageId = useAtomStateValue(calendarBookingPageIdState);
const permissionMap = usePermissionFlagGrantMap();
const permissionMap = usePermissionFlagMap();
const isAccountSyncEnabled =
permissionMap[PermissionFlagType.CONNECTED_ACCOUNTS];
@@ -1708,7 +1708,7 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
// currentUserWorkspaceState is now a Jotai state, set it directly
if (isRestricted === true) {
jotaiStore.set(currentUserWorkspaceState.atom, {
permissionFlagGrants: [],
permissionFlags: [],
twoFactorAuthenticationMethodSummary: null,
objectsPermissions: [
{
@@ -1725,7 +1725,7 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
});
} else {
jotaiStore.set(currentUserWorkspaceState.atom, {
permissionFlagGrants: [],
permissionFlags: [],
twoFactorAuthenticationMethodSummary: null,
objectsPermissions: [
{
@@ -24,7 +24,7 @@ import { useNextBillingPhase } from '@/settings/billing/hooks/useNextBillingPhas
import { useNextBillingSeats } from '@/settings/billing/hooks/useNextBillingSeats';
import { useNextPlan } from '@/settings/billing/hooks/useNextPlan';
import { useSplitPhaseItemsInPrices } from '@/settings/billing/hooks/useSplitPhaseItemsInPrices';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@@ -190,7 +190,7 @@ export const SettingsBillingSubscriptionInfo = ({
useEndSubscriptionTrialPeriod();
const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } =
usePermissionFlagGrantMap();
usePermissionFlagMap();
const seats = currentBillingSubscription.billingSubscriptionItems?.find(
(item) =>
@@ -1,5 +1,5 @@
import { useHasAccessTokenPair } from '@/auth/hooks/useHasAccessTokenPair';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { type ReactNode } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
@@ -22,7 +22,7 @@ export const SettingsProtectedRouteWrapper = ({
requiredFeatureFlag,
}: SettingsProtectedRouteWrapperProps) => {
const hasAccessTokenPair = useHasAccessTokenPair();
const hasPermission = useHasPermissionFlagGrant(settingsPermission);
const hasPermission = useHasPermissionFlag(settingsPermission);
const requiredFeatureFlagEnabled = useIsFeatureEnabled(
requiredFeatureFlag || null,
);
@@ -12,7 +12,7 @@ import {
import { currentUserState } from '@/auth/states/currentUserState';
import { billingState } from '@/client-config/states/billingState';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
import {
jotaiStore,
@@ -65,8 +65,8 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
</MockedProvider>
);
jest.mock('@/settings/roles/hooks/usePermissionFlagGrantMap', () => ({
usePermissionFlagGrantMap: jest.fn(),
jest.mock('@/settings/roles/hooks/usePermissionFlagMap', () => ({
usePermissionFlagMap: jest.fn(),
}));
jest.mock('@/domain-manager/hooks/useRedirectToWorkspaceDomain', () => ({
@@ -83,7 +83,7 @@ describe('useSettingsNavigationItems', () => {
});
it('should hide workspace settings when no permissions', () => {
(usePermissionFlagGrantMap as jest.Mock).mockImplementation(() => ({
(usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false,
[PermissionFlagType.DATA_MODEL]: false,
@@ -105,7 +105,7 @@ describe('useSettingsNavigationItems', () => {
});
it('should show workspace settings when has permissions', () => {
(usePermissionFlagGrantMap as jest.Mock).mockImplementation(() => ({
(usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: true,
[PermissionFlagType.WORKSPACE_MEMBERS]: true,
[PermissionFlagType.DATA_MODEL]: true,
@@ -127,7 +127,7 @@ describe('useSettingsNavigationItems', () => {
});
it('should show user section items regardless of permissions', () => {
(usePermissionFlagGrantMap as jest.Mock).mockImplementation(() => ({
(usePermissionFlagMap as jest.Mock).mockImplementation(() => ({
[PermissionFlagType.WORKSPACE]: false,
[PermissionFlagType.WORKSPACE_MEMBERS]: false,
[PermissionFlagType.DATA_MODEL]: false,
@@ -5,7 +5,7 @@ import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { billingState } from '@/client-config/states/billingState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { usePermissionFlagGrantMap } from '@/settings/roles/hooks/usePermissionFlagGrantMap';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import {
type NavigationDrawerItemIndentationLevel,
@@ -72,7 +72,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
supportChat?.supportDriver === 'FRONT' &&
isNonEmptyString(supportChat.supportFrontChatId);
const permissionMap = usePermissionFlagGrantMap();
const permissionMap = usePermissionFlagMap();
return [
{
label: t`User`,
@@ -14,10 +14,9 @@ export const useCanChangePassword = () => {
return { canChangePassword: true };
}
const hasBypassPermission =
currentUserWorkspace?.permissionFlagGrants?.includes(
PermissionFlagType.SSO_BYPASS,
);
const hasBypassPermission = currentUserWorkspace?.permissionFlags?.includes(
PermissionFlagType.SSO_BYPASS,
);
if (!hasBypassPermission) {
return { canChangePassword: false };
@@ -23,8 +23,8 @@ export const useCanEditProfileField = (field: EditableProfileField) => {
const editableFields = currentWorkspace.editableProfileFields ?? [];
const workspaceAllowsField = editableFields.includes(field);
const permissionFlagGrants = currentUserWorkspace.permissionFlagGrants ?? [];
const hasProfilePermission = permissionFlagGrants.includes(
const permissionFlags = currentUserWorkspace.permissionFlags ?? [];
const hasProfilePermission = permissionFlags.includes(
PermissionFlagType.PROFILE_INFORMATION,
);
@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const PERMISSION_FLAG_FRAGMENT = gql`
fragment PermissionFlagFragment on PermissionFlag {
id
flag
roleId
}
`;
@@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const PERMISSION_FLAG_GRANT_FRAGMENT = gql`
fragment PermissionFlagGrantFragment on PermissionFlagGrant {
id
flag
roleId
}
`;
@@ -1,15 +0,0 @@
import { PERMISSION_FLAG_GRANT_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagGrantFragment';
import { gql } from '@apollo/client';
export const UPSERT_PERMISSION_FLAG_GRANTS = gql`
${PERMISSION_FLAG_GRANT_FRAGMENT}
mutation UpsertPermissionFlagGrants(
$upsertPermissionFlagGrantsInput: UpsertPermissionFlagGrantsInput!
) {
upsertPermissionFlagGrants(
upsertPermissionFlagGrantsInput: $upsertPermissionFlagGrantsInput
) {
...PermissionFlagGrantFragment
}
}
`;
@@ -0,0 +1,15 @@
import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment';
import { gql } from '@apollo/client';
export const UPSERT_PERMISSION_FLAGS = gql`
${PERMISSION_FLAG_FRAGMENT}
mutation UpsertPermissionFlags(
$upsertPermissionFlagsInput: UpsertPermissionFlagsInput!
) {
upsertPermissionFlags(
upsertPermissionFlagsInput: $upsertPermissionFlagsInput
) {
...PermissionFlagFragment
}
}
`;
@@ -2,7 +2,7 @@ import { AGENT_FRAGMENT } from '@/ai/graphql/fragments/agentFragment';
import { API_KEY_FOR_ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/apiKeyForRoleFragment';
import { FIELD_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/fieldPermissionFragment';
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
import { PERMISSION_FLAG_GRANT_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagGrantFragment';
import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment';
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
import { ROW_LEVEL_PERMISSION_PREDICATE_FRAGMENT } from '@/settings/roles/graphql/fragments/rowLevelPermissionPredicateFragment';
import { ROW_LEVEL_PERMISSION_PREDICATE_GROUP_FRAGMENT } from '@/settings/roles/graphql/fragments/rowLevelPermissionPredicateGroupFragment';
@@ -14,7 +14,7 @@ export const GET_ROLES = gql`
${ROLE_FRAGMENT}
${AGENT_FRAGMENT}
${API_KEY_FOR_ROLE_FRAGMENT}
${PERMISSION_FLAG_GRANT_FRAGMENT}
${PERMISSION_FLAG_FRAGMENT}
${OBJECT_PERMISSION_FRAGMENT}
${FIELD_PERMISSION_FRAGMENT}
${ROW_LEVEL_PERMISSION_PREDICATE_FRAGMENT}
@@ -31,8 +31,8 @@ export const GET_ROLES = gql`
apiKeys {
...ApiKeyForRoleFragment
}
permissionFlagGrants {
...PermissionFlagGrantFragment
permissionFlags {
...PermissionFlagFragment
}
objectPermissions {
...ObjectPermissionFragment
@@ -6,24 +6,22 @@ import {
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
export const useHasPermissionFlagGrant = (
permissionFlagKey?: PermissionFlagType,
) => {
export const useHasPermissionFlag = (permissionFlag?: PermissionFlagType) => {
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
if (!permissionFlagKey) {
if (!permissionFlag) {
return true;
}
if (
permissionFlagKey === PermissionFlagType.WORKSPACE &&
permissionFlag === PermissionFlagType.WORKSPACE &&
currentWorkspace?.activationStatus ===
WorkspaceActivationStatus.PENDING_CREATION
) {
return true;
}
const userFlags = currentUserWorkspace?.permissionFlagGrants ?? [];
return userFlags.includes(permissionFlagKey);
const userFlags = currentUserWorkspace?.permissionFlags ?? [];
return userFlags.includes(permissionFlag);
};
@@ -3,14 +3,11 @@ import { PermissionFlagType } from '~/generated-metadata/graphql';
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
export const usePermissionFlagGrantMap = (): Record<
PermissionFlagType,
boolean
> => {
export const usePermissionFlagMap = (): Record<PermissionFlagType, boolean> => {
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
const currentUserWorkspaceSettingsPermissions =
currentUserWorkspace?.permissionFlagGrants;
currentUserWorkspace?.permissionFlags;
const initialPermissions = buildRecordFromKeysWithSameValue(
Object.values(PermissionFlagType),
@@ -1,7 +1,7 @@
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader';
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow';
import { useSettingsRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagGrantConfig';
import { useSettingsRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
@@ -42,7 +42,7 @@ export const SettingsRolePermissionsSettingsSection = ({
roleId,
);
const settingsPermissionsConfig = useSettingsRolePermissionFlagGrantConfig({
const settingsPermissionsConfig = useSettingsRolePermissionFlagConfig({
assignmentCapabilities: {
canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents,
canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers,
@@ -31,15 +31,15 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
);
const allSettingsPermissionsEnabled = settingsPermissionsConfig.every(
(permission) =>
settingsDraftRole.permissionFlagGrants?.some(
(permissionFlagGrant) => permissionFlagGrant.flag === permission.key,
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
),
);
const someSettingsPermissionsEnabled = settingsPermissionsConfig.some(
(permission) =>
settingsDraftRole.permissionFlagGrants?.some(
(permissionFlagGrant) => permissionFlagGrant.flag === permission.key,
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
),
);
@@ -64,7 +64,7 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
setSettingsDraftRole({
...settingsDraftRole,
permissionFlagGrants: newValue
permissionFlags: newValue
? settingsPermissionsConfig.map((permission) => ({
id: v4(),
flag: permission.key,
@@ -47,8 +47,8 @@ export const SettingsRolePermissionsSettingsTableRow = ({
);
const isPermissionEnabled =
settingsDraftRole.permissionFlagGrants?.some(
(permissionFlagGrant) => permissionFlagGrant.flag === permission.key,
settingsDraftRole.permissionFlags?.some(
(permissionFlag) => permissionFlag.flag === permission.key,
) ?? false;
const isAllSettingsOverride =
@@ -64,12 +64,12 @@ export const SettingsRolePermissionsSettingsTableRow = ({
);
const handleChange = (value: boolean) => {
const currentPermissions = settingsDraftRole.permissionFlagGrants ?? [];
const currentPermissions = settingsDraftRole.permissionFlags ?? [];
if (value === true) {
setSettingsDraftRole({
...settingsDraftRole,
permissionFlagGrants: [
permissionFlags: [
...currentPermissions,
{
id: v4(),
@@ -81,7 +81,7 @@ export const SettingsRolePermissionsSettingsTableRow = ({
} else {
setSettingsDraftRole({
...settingsDraftRole,
permissionFlagGrants: currentPermissions.filter(
permissionFlags: currentPermissions.filter(
(p) => p.flag !== permission.key,
),
});
@@ -1,7 +1,7 @@
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader';
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow';
import { useActionRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagGrantConfig';
import { useActionRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
@@ -43,7 +43,7 @@ export const SettingsRolePermissionsToolSection = ({
roleId,
);
const toolPermissionsConfig = useActionRolePermissionFlagGrantConfig({
const toolPermissionsConfig = useActionRolePermissionFlagConfig({
assignmentCapabilities: {
canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents,
canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers,
@@ -15,7 +15,7 @@ import {
} from 'twenty-ui/display';
import { PermissionFlagType } from '~/generated-metadata/graphql';
type UseActionRolePermissionFlagGrantConfigParams = {
type UseActionRolePermissionFlagConfigParams = {
assignmentCapabilities?: {
canBeAssignedToAgents?: boolean;
canBeAssignedToUsers?: boolean;
@@ -23,9 +23,9 @@ type UseActionRolePermissionFlagGrantConfigParams = {
};
};
export const useActionRolePermissionFlagGrantConfig = ({
export const useActionRolePermissionFlagConfig = ({
assignmentCapabilities,
}: UseActionRolePermissionFlagGrantConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
}: UseActionRolePermissionFlagConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
const {
canBeAssignedToAgents = false,
canBeAssignedToUsers = false,
@@ -18,7 +18,7 @@ import {
} from 'twenty-ui/display';
import { PermissionFlagType } from '~/generated-metadata/graphql';
type UseSettingsRolePermissionFlagGrantConfigParams = {
type UseSettingsRolePermissionFlagConfigParams = {
assignmentCapabilities?: {
canBeAssignedToAgents?: boolean;
canBeAssignedToUsers?: boolean;
@@ -26,9 +26,9 @@ type UseSettingsRolePermissionFlagGrantConfigParams = {
};
};
export const useSettingsRolePermissionFlagGrantConfig = ({
export const useSettingsRolePermissionFlagConfig = ({
assignmentCapabilities,
}: UseSettingsRolePermissionFlagGrantConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
}: UseSettingsRolePermissionFlagConfigParams = {}): SettingsRolePermissionsSettingPermission[] => {
const {
canBeAssignedToAgents = false,
canBeAssignedToUsers = false,
@@ -19,7 +19,7 @@ import {
UpdateOneRoleDocument,
UpsertFieldPermissionsDocument,
UpsertObjectPermissionsDocument,
UpsertPermissionFlagGrantsDocument,
UpsertPermissionFlagsDocument,
} from '~/generated-metadata/graphql';
import { getDirtyFields } from '~/utils/getDirtyFields';
@@ -49,9 +49,7 @@ export const useSaveDraftRoleToDB = ({
}) => {
const [createRole] = useMutation(CreateOneRoleDocument);
const [updateRole] = useMutation(UpdateOneRoleDocument);
const [upsertPermissionFlagGrants] = useMutation(
UpsertPermissionFlagGrantsDocument,
);
const [upsertPermissionFlags] = useMutation(UpsertPermissionFlagsDocument);
const [upsertObjectPermissions] = useMutation(
UpsertObjectPermissionsDocument,
);
@@ -166,14 +164,14 @@ export const useSaveDraftRoleToDB = ({
};
const updateExistingRole = async () => {
if (isDefined(dirtyFields.permissionFlagGrants)) {
await upsertPermissionFlagGrants({
if (isDefined(dirtyFields.permissionFlags)) {
await upsertPermissionFlags({
variables: {
upsertPermissionFlagGrantsInput: {
upsertPermissionFlagsInput: {
roleId: roleId,
permissionFlagGrantKeys:
settingsDraftRole.permissionFlagGrants?.map(
(permissionFlagGrant) => permissionFlagGrant.flag,
permissionFlagKeys:
settingsDraftRole.permissionFlags?.map(
(permissionFlag) => permissionFlag.flag,
) ?? [],
},
},
@@ -363,14 +361,14 @@ export const useSaveDraftRoleToDB = ({
};
const upsertRolePermissions = async (targetRoleId: string) => {
if (isDefined(dirtyFields.permissionFlagGrants)) {
await upsertPermissionFlagGrants({
if (isDefined(dirtyFields.permissionFlags)) {
await upsertPermissionFlags({
variables: {
upsertPermissionFlagGrantsInput: {
upsertPermissionFlagsInput: {
roleId: targetRoleId,
permissionFlagGrantKeys:
settingsDraftRole.permissionFlagGrants?.map(
(permissionFlagGrant) => permissionFlagGrant.flag,
permissionFlagKeys:
settingsDraftRole.permissionFlags?.map(
(permissionFlag) => permissionFlag.flag,
) ?? [],
},
},
@@ -20,7 +20,7 @@ export const settingsDraftRoleFamilyState = createAtomFamilyState<
canAccessAllTools: false,
isEditable: false,
workspaceMembers: [],
permissionFlagGrants: [],
permissionFlags: [],
objectPermissions: [],
fieldPermissions: [],
rowLevelPermissionPredicates: [],
@@ -34,7 +34,7 @@ export const USER_QUERY_FRAGMENT = gql`
}
currentUserWorkspace {
id
permissionFlagGrants
permissionFlags
objectsPermissions {
...ObjectPermissionFragment
}
@@ -74,8 +74,7 @@ export const useLoadCurrentUser = () => {
if (isDefined(user.currentUserWorkspace)) {
setCurrentUserWorkspace({
permissionFlagGrants:
user.currentUserWorkspace.permissionFlagGrants ?? [],
permissionFlags: user.currentUserWorkspace.permissionFlags ?? [],
twoFactorAuthenticationMethodSummary:
user.currentUserWorkspace.twoFactorAuthenticationMethodSummary ?? [],
objectsPermissions:
@@ -1,4 +1,4 @@
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
import {
ViewVisibility,
@@ -7,9 +7,7 @@ import {
export const useCanPersistViewChanges = () => {
const { currentView } = useGetCurrentViewOnly();
const hasViewsPermission = useHasPermissionFlagGrant(
PermissionFlagType.VIEWS,
);
const hasViewsPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
if (!currentView) {
return { canPersistChanges: false };
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
@@ -77,7 +77,7 @@ export const ViewPickerContentEffect = () => {
const { availableFieldsForGrouping } =
useGetAvailableFieldsToGroupRecordsBy();
const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar();
const hasViewPermission = useHasPermissionFlagGrant(PermissionFlagType.VIEWS);
const hasViewPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
useEffect(() => {
if (
@@ -1,6 +1,6 @@
import { useCreateManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useCreateManyNavigationMenuItems';
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
@@ -53,9 +53,7 @@ export const ViewPickerOptionDropdown = ({
const setViewPickerReferenceViewId = useSetAtomComponentState(
viewPickerReferenceViewIdComponentState,
);
const hasViewsPermission = useHasPermissionFlagGrant(
PermissionFlagType.VIEWS,
);
const hasViewsPermission = useHasPermissionFlag(PermissionFlagType.VIEWS);
const { createManyNavigationMenuItems } = useCreateManyNavigationMenuItems();
const { navigationMenuItems, currentWorkspaceMemberId } =
@@ -6,21 +6,21 @@ import { WorkflowAiAgentPermissionsFlagRow } from './WorkflowAiAgentPermissionsF
type WorkflowAiAgentPermissionsFlagListProps = {
title: string;
permissions: SettingsRolePermissionsSettingPermission[];
enabledPermissionFlagGrantKeys: PermissionFlagType[];
enabledPermissionFlagKeys: PermissionFlagType[];
readonly: boolean;
showDeleteButton?: boolean;
onAddPermissionFlagGrant?: (permissionKey: PermissionFlagType) => void;
onDeletePermissionFlagGrant?: (permissionKey: PermissionFlagType) => void;
onAddPermissionFlag?: (permissionKey: PermissionFlagType) => void;
onDeletePermissionFlag?: (permissionKey: PermissionFlagType) => void;
};
export const WorkflowAiAgentPermissionsFlagList = ({
title,
permissions,
enabledPermissionFlagGrantKeys,
enabledPermissionFlagKeys,
readonly,
showDeleteButton = false,
onAddPermissionFlagGrant,
onDeletePermissionFlagGrant,
onAddPermissionFlag,
onDeletePermissionFlag,
}: WorkflowAiAgentPermissionsFlagListProps) => {
if (permissions.length === 0) {
return null;
@@ -29,9 +29,7 @@ export const WorkflowAiAgentPermissionsFlagList = ({
return (
<SidePanelGroup heading={title}>
{permissions.map((permission) => {
const isEnabled = enabledPermissionFlagGrantKeys.includes(
permission.key,
);
const isEnabled = enabledPermissionFlagKeys.includes(permission.key);
return (
<WorkflowAiAgentPermissionsFlagRow
@@ -40,8 +38,8 @@ export const WorkflowAiAgentPermissionsFlagList = ({
isEnabled={isEnabled}
readonly={readonly}
showDeleteButton={showDeleteButton}
onAdd={() => onAddPermissionFlagGrant?.(permission.key)}
onDelete={() => onDeletePermissionFlagGrant?.(permission.key)}
onAdd={() => onAddPermissionFlag?.(permission.key)}
onDelete={() => onDeletePermissionFlag?.(permission.key)}
/>
);
})}
@@ -1,5 +1,5 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useActionRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagGrantConfig';
import { useActionRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig';
import { SidePanelSubPageNavigationHeader } from '@/side-panel/pages/common/components/SidePanelSubPageNavigationHeader';
import { TextInput } from '@/ui/input/components/TextInput';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@@ -98,14 +98,12 @@ export const WorkflowAiAgentPermissionsTab = ({
(item) => item.id === workflowAiAgentActionAgent?.roleId,
);
const objectPermissions = role?.objectPermissions || [];
const permissionFlagGrantKeys =
role?.permissionFlagGrants?.map(
(permissionFlagGrant) => permissionFlagGrant.flag,
) ?? [];
const permissionFlagKeys =
role?.permissionFlags?.map((permissionFlag) => permissionFlag.flag) ?? [];
const hasRoleWithPermissions =
isDefined(workflowAiAgentActionAgent?.roleId) &&
(objectPermissions.length > 0 || permissionFlagGrantKeys.length > 0);
const actionPermissionsConfig = useActionRolePermissionFlagGrantConfig({
(objectPermissions.length > 0 || permissionFlagKeys.length > 0);
const actionPermissionsConfig = useActionRolePermissionFlagConfig({
assignmentCapabilities: { canBeAssignedToAgents: true },
});
@@ -121,7 +119,7 @@ export const WorkflowAiAgentPermissionsTab = ({
filteredEnabledPermissions: filteredEnabledActionPermissions,
} = getFilteredPermissions({
permissions: actionPermissionsConfig,
permissionFlagGrantKeys,
permissionFlagKeys,
searchQuery,
});
@@ -136,12 +134,12 @@ export const WorkflowAiAgentPermissionsTab = ({
const {
handleAddPermission,
handleDeletePermission,
handleAddPermissionFlagGrant,
handleDeletePermissionFlagGrant,
handleAddPermissionFlag,
handleDeletePermissionFlag,
} = useWorkflowAiAgentPermissionActions({
readonly,
objectPermissions,
permissionFlagGrantKeys,
permissionFlagKeys,
refetchAgentAndRoles,
});
@@ -239,9 +237,9 @@ export const WorkflowAiAgentPermissionsTab = ({
<WorkflowAiAgentPermissionsFlagList
title={t`Actions`}
permissions={filteredActionPermissions}
enabledPermissionFlagGrantKeys={permissionFlagGrantKeys}
enabledPermissionFlagKeys={permissionFlagKeys}
readonly={readonly}
onAddPermissionFlagGrant={handleAddPermissionFlagGrant}
onAddPermissionFlag={handleAddPermissionFlag}
/>
</>
)}
@@ -257,10 +255,10 @@ export const WorkflowAiAgentPermissionsTab = ({
<WorkflowAiAgentPermissionsFlagList
title={t`Actions`}
permissions={filteredEnabledActionPermissions}
enabledPermissionFlagGrantKeys={permissionFlagGrantKeys}
enabledPermissionFlagKeys={permissionFlagKeys}
readonly={readonly}
showDeleteButton={!readonly}
onDeletePermissionFlagGrant={handleDeletePermissionFlagGrant}
onDeletePermissionFlag={handleDeletePermissionFlag}
/>
</>
)}
@@ -10,11 +10,11 @@ export const getFilteredPermissions = <
T extends { key: PermissionFlagType; name: string },
>({
permissions,
permissionFlagGrantKeys,
permissionFlagKeys,
searchQuery,
}: {
permissions: T[];
permissionFlagGrantKeys: PermissionFlagType[];
permissionFlagKeys: PermissionFlagType[];
searchQuery: string;
}): FilteredPermissionsResult<T> => {
const filteredPermissions = filterBySearchQuery<T>({
@@ -24,7 +24,7 @@ export const getFilteredPermissions = <
});
const enabledPermissions = permissions.filter((permission) =>
permissionFlagGrantKeys.includes(permission.key),
permissionFlagKeys.includes(permission.key),
);
const filteredEnabledPermissions = filterBySearchQuery<T>({
@@ -1,7 +1,7 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { type SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/SettingsRoleObjectPermissionIconConfig';
import { useActionRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagGrantConfig';
import { useSettingsRolePermissionFlagGrantConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagGrantConfig';
import { useActionRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig';
import { useSettingsRolePermissionFlagConfig } from '@/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CRUD_PERMISSIONS } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentCrudPermissions';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@@ -20,20 +20,20 @@ import {
AssignRoleToAgentDocument,
CreateOneRoleDocument,
UpsertObjectPermissionsDocument,
UpsertPermissionFlagGrantsDocument,
UpsertPermissionFlagsDocument,
} from '~/generated-metadata/graphql';
type UseWorkflowAiAgentPermissionActionsParams = {
readonly: boolean;
objectPermissions: ObjectPermission[];
permissionFlagGrantKeys: PermissionFlagType[];
permissionFlagKeys: PermissionFlagType[];
refetchAgentAndRoles: () => Promise<{ refetchedAgent?: Agent }>;
};
export const useWorkflowAiAgentPermissionActions = ({
readonly,
objectPermissions,
permissionFlagGrantKeys,
permissionFlagKeys,
refetchAgentAndRoles,
}: UseWorkflowAiAgentPermissionActionsParams) => {
const { enqueueSuccessSnackBar } = useSnackBar();
@@ -41,10 +41,10 @@ export const useWorkflowAiAgentPermissionActions = ({
useAtomState(workflowAiAgentActionAgentState);
const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
useFilteredObjectMetadataItems();
const settingsPermissionsConfig = useSettingsRolePermissionFlagGrantConfig({
const settingsPermissionsConfig = useSettingsRolePermissionFlagConfig({
assignmentCapabilities: { canBeAssignedToAgents: true },
});
const actionPermissionsConfig = useActionRolePermissionFlagGrantConfig({
const actionPermissionsConfig = useActionRolePermissionFlagConfig({
assignmentCapabilities: { canBeAssignedToAgents: true },
});
@@ -60,13 +60,11 @@ export const useWorkflowAiAgentPermissionActions = ({
const [upsertObjectPermissions] = useMutation(
UpsertObjectPermissionsDocument,
);
const [upsertPermissionFlagGrants] = useMutation(
UpsertPermissionFlagGrantsDocument,
);
const [upsertPermissionFlags] = useMutation(UpsertPermissionFlagsDocument);
const roleId = workflowAiAgentActionAgent?.roleId;
const permissionFlagGrantLabelMap = useMemo(
const permissionFlagLabelMap = useMemo(
() =>
[...settingsPermissionsConfig, ...actionPermissionsConfig].reduce<
Partial<Record<PermissionFlagType, string>>
@@ -298,8 +296,8 @@ export const useWorkflowAiAgentPermissionActions = ({
}
};
const handleAddPermissionFlagGrant = async (
permissionFlagGrantKey: PermissionFlagType,
const handleAddPermissionFlag = async (
permissionFlagKey: PermissionFlagType,
) => {
if (readonly) {
return;
@@ -307,21 +305,15 @@ export const useWorkflowAiAgentPermissionActions = ({
const ensuredRoleId = await ensureRoleId();
if (
!ensuredRoleId ||
permissionFlagGrantKeys.includes(permissionFlagGrantKey)
) {
if (!ensuredRoleId || permissionFlagKeys.includes(permissionFlagKey)) {
return;
}
await upsertPermissionFlagGrants({
await upsertPermissionFlags({
variables: {
upsertPermissionFlagGrantsInput: {
upsertPermissionFlagsInput: {
roleId: ensuredRoleId,
permissionFlagGrantKeys: [
...permissionFlagGrantKeys,
permissionFlagGrantKey,
],
permissionFlagKeys: [...permissionFlagKeys, permissionFlagKey],
},
},
});
@@ -331,33 +323,33 @@ export const useWorkflowAiAgentPermissionActions = ({
setWorkflowAiAgentPermissionsSelectedObjectId(undefined);
};
const handleDeletePermissionFlagGrant = async (
permissionFlagGrantKey: PermissionFlagType,
const handleDeletePermissionFlag = async (
permissionFlagKey: PermissionFlagType,
) => {
if (!isDefined(roleId) || readonly) {
return;
}
if (!permissionFlagGrantKeys.includes(permissionFlagGrantKey)) {
if (!permissionFlagKeys.includes(permissionFlagKey)) {
return;
}
const permissionFlagGrantKeysAfterRemoval = permissionFlagGrantKeys.filter(
(flag) => flag !== permissionFlagGrantKey,
const permissionFlagKeysAfterRemoval = permissionFlagKeys.filter(
(flag) => flag !== permissionFlagKey,
);
await upsertPermissionFlagGrants({
await upsertPermissionFlags({
variables: {
upsertPermissionFlagGrantsInput: {
upsertPermissionFlagsInput: {
roleId,
permissionFlagGrantKeys: permissionFlagGrantKeysAfterRemoval,
permissionFlagKeys: permissionFlagKeysAfterRemoval,
},
},
});
await refetchAgentAndRoles();
const permissionLabel = permissionFlagGrantLabelMap[permissionFlagGrantKey];
const permissionLabel = permissionFlagLabelMap[permissionFlagKey];
if (isDefined(permissionLabel)) {
enqueueSuccessSnackBar({
@@ -369,7 +361,7 @@ export const useWorkflowAiAgentPermissionActions = ({
return {
handleAddPermission,
handleDeletePermission,
handleAddPermissionFlagGrant,
handleDeletePermissionFlagGrant,
handleAddPermissionFlag,
handleDeletePermissionFlag,
};
};
@@ -10,7 +10,7 @@ import { getWorkflowCodeFieldsEnumSelectOptions } from '@/workflow/workflow-step
import { getWorkflowCodeFieldsLeafKind } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsLeafKind';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { isNonEmptyArray, isObject } from '@sniptt/guards';
import { isNonEmptyArray, isNonEmptyString, isObject } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { type FunctionInput, type InputSchema } from 'twenty-shared/workflow';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -51,10 +51,18 @@ export const WorkflowEditActionCodeFields = ({
const currentPath = [...path, inputKey];
const pathKey = currentPath.join('.');
const schemaProperty = getInputSchemaPropertyAtPath(
inputSchema,
currentPath,
);
const displayLabel = isNonEmptyString(schemaProperty?.label)
? schemaProperty.label
: inputKey;
if (inputValue !== null && isObject(inputValue)) {
return (
<div key={pathKey}>
<InputLabel>{inputKey}</InputLabel>
<InputLabel>{displayLabel}</InputLabel>
<FormNestedFieldInputContainer>
<WorkflowEditActionCodeFields
functionInput={inputValue}
@@ -70,17 +78,13 @@ export const WorkflowEditActionCodeFields = ({
);
}
const schemaProperty = getInputSchemaPropertyAtPath(
inputSchema,
currentPath,
);
const leafKind = getWorkflowCodeFieldsLeafKind(schemaProperty);
if (leafKind === 'boolean') {
return (
<FormBooleanFieldInput
key={pathKey}
label={inputKey}
label={displayLabel}
defaultValue={
!isDefined(inputValue)
? undefined
@@ -100,7 +104,7 @@ export const WorkflowEditActionCodeFields = ({
return (
<FormNumberFieldInput
key={pathKey}
label={inputKey}
label={displayLabel}
defaultValue={
!isDefined(inputValue)
? undefined
@@ -124,7 +128,7 @@ export const WorkflowEditActionCodeFields = ({
return (
<FormSelectFieldInput
key={pathKey}
label={inputKey}
label={displayLabel}
defaultValue={
!isDefined(inputValue)
? undefined
@@ -144,7 +148,7 @@ export const WorkflowEditActionCodeFields = ({
return (
<FormTextFieldInput
key={pathKey}
label={inputKey}
label={displayLabel}
placeholder={t`Enter value`}
defaultValue={inputValue ? `${inputValue}` : ''}
readonly={readonly}
@@ -130,7 +130,7 @@ export const SettingsAgentRoleTab = ({
apiKeys: [],
objectPermissions: [],
fieldPermissions: [],
permissionFlagGrants: [],
permissionFlags: [],
rowLevelPermissionPredicateGroups: [],
rowLevelPermissionPredicates: [],
});
@@ -6,7 +6,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -112,7 +112,7 @@ export const SettingsApplicationDetails = () => {
const { upgrade, isUpgrading } = useUpgradeApplication();
const canInstallMarketplaceApps = useHasPermissionFlagGrant(
const canInstallMarketplaceApps = useHasPermissionFlag(
PermissionFlagType.MARKETPLACE_APPS,
);
@@ -1,4 +1,4 @@
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -22,7 +22,7 @@ const APPLICATIONS_TAB_LIST_ID = 'applications-tab-list';
export const SettingsApplications = () => {
const { t } = useLingui();
const hasDeveloperAccess = useHasPermissionFlagGrant(
const hasDeveloperAccess = useHasPermissionFlag(
PermissionFlagType.API_KEYS_AND_WEBHOOKS,
);
@@ -2,7 +2,7 @@ import { CurrentApplicationContext } from '@/applications/contexts/CurrentApplic
import { useInstallMarketplaceApp } from '@/marketplace/hooks/useInstallMarketplaceApp';
import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
@@ -46,7 +46,7 @@ export const SettingsAvailableApplicationDetails = () => {
const { install, isInstalling } = useInstallMarketplaceApp();
const { upgrade, isUpgrading } = useUpgradeApplication();
const canInstallMarketplaceApps = useHasPermissionFlagGrant(
const canInstallMarketplaceApps = useHasPermissionFlag(
PermissionFlagType.MARKETPLACE_APPS,
);
@@ -121,12 +121,12 @@ const buildSyntheticRole = (
canReadFieldValue: permission.canReadFieldValue,
canUpdateFieldValue: permission.canUpdateFieldValue,
})),
permissionFlagGrants: (defaultRole.permissionFlagGrants ?? []).map(
(permissionFlagGrant) => ({
__typename: 'PermissionFlagGrant' as const,
permissionFlags: (defaultRole.permissionFlags ?? []).map(
(permissionFlag) => ({
__typename: 'PermissionFlag' as const,
id: uuidv4(),
roleId: defaultRole.universalIdentifier,
flag: permissionFlagGrant.flag,
flag: permissionFlag.flag,
}),
),
});
@@ -6,7 +6,7 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useImpersonationSession } from '@/auth/hooks/useImpersonationSession';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@@ -54,8 +54,7 @@ export const SettingsWorkspaceMember = () => {
const [impersonate] = useMutation(ImpersonateDocument);
const isImpersonating = useAtomStateValue(isImpersonatingState);
const canImpersonate =
useHasPermissionFlagGrant(PermissionFlagType.IMPERSONATE) &&
!isImpersonating;
useHasPermissionFlag(PermissionFlagType.IMPERSONATE) && !isImpersonating;
const {
roles,
@@ -3,7 +3,7 @@ import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { IconLock, IconUserPlus, IconUsers } from 'twenty-ui/display';
import { useHasPermissionFlagGrant } from '@/settings/roles/hooks/useHasPermissionFlagGrant';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -23,9 +23,7 @@ const MEMBERS_TAB_ROLES_ID = 'roles';
export const SettingsWorkspaceMembers = () => {
const { t } = useLingui();
const hasRolesPermission = useHasPermissionFlagGrant(
PermissionFlagType.ROLES,
);
const hasRolesPermission = useHasPermissionFlag(PermissionFlagType.ROLES);
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
@@ -743,7 +743,7 @@ export const mockedMinimalMetadata: MinimalMetadata =
"hash": "b022456a-bd5b-4314-9fb5-0111c80a8649"
},
{
"collectionName": "permissionFlagGrant",
"collectionName": "permissionFlag",
"hash": "2de6c5b6-3b5c-4f61-8426-92c3d4c5daac"
},
{
@@ -23,7 +23,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": false,
"canBeAssignedToApiKeys": true,
"permissionFlagGrants": [],
"permissionFlags": [],
"objectPermissions": [],
"fieldPermissions": [],
"rowLevelPermissionPredicates": [],
@@ -68,7 +68,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": false,
"canBeAssignedToApiKeys": false,
"permissionFlagGrants": [],
"permissionFlags": [],
"objectPermissions": [],
"fieldPermissions": [],
"rowLevelPermissionPredicates": [],
@@ -105,7 +105,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": true,
"canBeAssignedToApiKeys": true,
"permissionFlagGrants": [],
"permissionFlags": [],
"objectPermissions": [
{
"__typename": "ObjectPermission",
@@ -184,7 +184,7 @@ export const mockedRoles: Role[] =
"canBeAssignedToUsers": true,
"canBeAssignedToAgents": false,
"canBeAssignedToApiKeys": false,
"permissionFlagGrants": [],
"permissionFlags": [],
"objectPermissions": [],
"fieldPermissions": [],
"rowLevelPermissionPredicates": [],
@@ -199,7 +199,7 @@ export const mockedUserData: MockedUser = {
workspaceMember: mockedWorkspaceMemberData,
currentWorkspace: mockCurrentWorkspace,
currentUserWorkspace: {
permissionFlagGrants: [
permissionFlags: [
PermissionFlagType.WORKSPACE_MEMBERS,
PermissionFlagType.CONNECTED_ACCOUNTS,
],
@@ -269,7 +269,7 @@ export const mockedOnboardingUserData = (
workspaceMembers: [],
currentWorkspace: mockCurrentWorkspace,
currentUserWorkspace: {
permissionFlagGrants: [PermissionFlagType.WORKSPACE_MEMBERS],
permissionFlags: [PermissionFlagType.WORKSPACE_MEMBERS],
objectPermissions: getTestEnrichedObjectMetadataItemsMock().map(
(item) => ({
objectMetadataId: item.id,
@@ -330,7 +330,7 @@ export const EXPECTED_MANIFEST: Manifest = {
canBeAssignedToApiKeys: false,
fieldPermissions: [],
objectPermissions: [],
permissionFlagGrants: [],
permissionFlags: [],
},
],
};
@@ -1385,7 +1385,7 @@ export const EXPECTED_MANIFEST: Manifest = {
universalIdentifier: 'c0c1c2c3-c4c5-4000-8000-000000000001',
fieldPermissions: [],
objectPermissions: [],
permissionFlagGrants: [],
permissionFlags: [],
},
{
canBeAssignedToAgents: false,
@@ -1417,7 +1417,7 @@ export const EXPECTED_MANIFEST: Manifest = {
objectUniversalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
},
],
permissionFlagGrants: [
permissionFlags: [
{
universalIdentifier: '01d7865a-7700-5d49-b2aa-62623c2cbac7',
flag: PermissionFlagType.APPLICATIONS,
@@ -36,7 +36,7 @@ const buildMockCommandMenuContextApi = (
},
selectedRecords: [],
featureFlags: {},
permissionFlagGrants: {},
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
objectMetadataItem: {},
@@ -13,7 +13,7 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1
"ObjectRecordGroupByDateGranularity",
"OnDeleteAction",
"PageLayoutTabLayoutMode",
"PermissionFlagGrant",
"PermissionFlag",
"RelationType",
"STANDARD_OBJECT",
"STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS",
@@ -28,13 +28,13 @@ export const fromRoleConfigToRoleManifest = (
),
}),
),
permissionFlagGrants: (roleConfig.permissionFlagGrants ?? []).map(
(permissionFlagGrant) => ({
permissionFlags: (roleConfig.permissionFlags ?? []).map(
(permissionFlag) => ({
universalIdentifier: uuidv5(
`${roleConfig.universalIdentifier}:${permissionFlagGrant}`,
`${roleConfig.universalIdentifier}:${permissionFlag}`,
ROLE_UNIVERSAL_IDENTIFIER_NAMESPACE,
),
flag: permissionFlagGrant,
flag: permissionFlag,
}),
),
};
+1 -1
View File
@@ -86,7 +86,7 @@ export type {
export { defineApplicationRole } from '@/sdk/define/roles/define-application-role';
export { defineRole } from '@/sdk/define/roles/define-role';
export { PermissionFlagGrant } from '@/sdk/define/roles/permission-flag-type';
export { PermissionFlag } from '@/sdk/define/roles/permission-flag-type';
export { defineSkill } from '@/sdk/define/skills/define-skill';
@@ -32,16 +32,16 @@ describe('defineApplicationRole', () => {
expect(result.config?.canReadAllObjectRecords).toBe(true);
});
it('should accept permissionFlagGrants', () => {
it('should accept permissionFlags', () => {
const config = {
...validConfig,
permissionFlagGrants: ['UPLOAD_FILE'],
permissionFlags: ['UPLOAD_FILE'],
};
const result = defineApplicationRole(config as any);
expect(result.success).toBe(true);
expect(result.config?.permissionFlagGrants).toHaveLength(1);
expect(result.config?.permissionFlags).toHaveLength(1);
});
it('should return error when universalIdentifier is missing', () => {
@@ -32,16 +32,16 @@ describe('defineRole', () => {
expect(result.config?.canReadAllObjectRecords).toBe(true);
});
it('should accept permissionFlagGrants', () => {
it('should accept permissionFlags', () => {
const config = {
...validConfig,
permissionFlagGrants: ['UPLOAD_FILE', 'DOWNLOAD_FILE'],
permissionFlags: ['UPLOAD_FILE', 'DOWNLOAD_FILE'],
};
const result = defineRole(config as any);
expect(result.success).toBe(true);
expect(result.config?.permissionFlagGrants).toHaveLength(2);
expect(result.config?.permissionFlags).toHaveLength(2);
});
it('should return error when universalIdentifier is missing', () => {
@@ -1 +1 @@
export { PermissionFlagType as PermissionFlagGrant } from 'twenty-shared/constants';
export { PermissionFlagType as PermissionFlag } from 'twenty-shared/constants';
@@ -7,9 +7,9 @@ import { type PermissionFlagType } from 'twenty-shared/constants';
export type RoleConfig = Omit<
RoleManifest,
'objectPermissions' | 'fieldPermissions' | 'permissionFlagGrants'
'objectPermissions' | 'fieldPermissions' | 'permissionFlags'
> & {
objectPermissions?: Omit<ObjectPermissionManifest, 'universalIdentifier'>[];
fieldPermissions?: Omit<FieldPermissionManifest, 'universalIdentifier'>[];
permissionFlagGrants?: PermissionFlagType[];
permissionFlags?: PermissionFlagType[];
};
@@ -33,7 +33,6 @@ import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permi
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { PermissionFlagGrantEntity } from 'src/engine/metadata-modules/permission-flag-grant/permission-flag-grant.entity';
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
@@ -68,7 +67,7 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget<ObjectLiteral>[] = [
ViewFilterGroupEntity,
FieldPermissionEntity,
ObjectPermissionEntity,
PermissionFlagGrantEntity,
PermissionFlagEntity,
RoleTargetEntity,
SearchFieldMetadataEntity,
RowLevelPermissionPredicateEntity,
@@ -83,7 +82,6 @@ const WORKSPACE_RELATED_ENTITIES: EntityTarget<ObjectLiteral>[] = [
// Level 2: Children that depend on core entities
FieldMetadataEntity,
PageLayoutEntity,
PermissionFlagEntity,
SkillEntity,
LogicFunctionEntity,

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