[FRONT COMPONENTS] Introduce conditionalAvailabilityExpression to command menu items (#18319)

## PR Description

- Uses `expr-eval` to enable front components (SDK plugins) to define
conditional availability as declarative expressions.
- Moves shared types and constants to `twenty-shared`
- Introduces a `conditionalAvailabilityExpression` field on
`CommandMenuItemEntity`, allowing command menu items to store an
`expr-eval` compatible expression string that is evaluated against a
CommandMenuContext to determine if the item should be shown.
- Creates an esbuild transform plugin
`conditional-availability-transform-plugin` in `twenty-sdk` that
converts TypeScript conditional availability expressions into
`expr-eval` compatible syntax at build time, so SDK developers can write
natural TS expressions that get transformed to evaluable strings.
- Removes deprecated `forceRegisteredActionsByKey` state and its usage.
- Creates `useCommandMenuContext` hook that builds the full
`CommandMenuContext` object from React state, which is then passed to
`useCommandMenuItemFrontComponentActions` for evaluating conditional
availability expressions.
This commit is contained in:
Raphaël Bosi
2026-03-04 16:33:58 +01:00
committed by GitHub
parent f09a9cc25a
commit b11f77df2a
349 changed files with 1648 additions and 454 deletions
@@ -2,9 +2,9 @@ import gql from 'graphql-tag';
import request from 'supertest';
import { makeMetadataAPIRequestWithFileUpload } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-file-upload.util';
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
import { FeatureFlagKey } from 'twenty-shared/types';
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
@@ -1,6 +1,5 @@
import gql from 'graphql-tag';
import { type FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type FeatureFlagKey } from 'twenty-shared/types';
export const updateFeatureFlagFactory = (
workspaceId: string,
@@ -6,8 +6,7 @@ import { syncApplication } from 'test/integration/metadata/suites/application/ut
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { v4 as uuidv4 } from 'uuid';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagKey } from 'twenty-shared/types';
const INVALID_UUID_APP_ID = uuidv4();
const INVALID_UUID_ROLE_ID = uuidv4();
@@ -6,8 +6,8 @@ import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { FeatureFlagKey } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type CreateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/create-command-menu-item.input';
type TestContext = {
@@ -7,8 +7,7 @@ import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagKey } from 'twenty-shared/types';
type TestContext = {
input: DeleteCommandMenuItemFactoryInput;
@@ -8,8 +8,8 @@ import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { FeatureFlagKey } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type UpdateCommandMenuItemInput } from 'src/engine/metadata-modules/command-menu-item/dtos/update-command-menu-item.input';
type TestContext = {
@@ -7,8 +7,8 @@ import { seedBuiltFrontComponentFile } from 'test/integration/metadata/suites/fr
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
import { FeatureFlagKey } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
describe('CommandMenuItem creation should succeed', () => {
@@ -3,8 +3,7 @@ import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update
import { createCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/create-command-menu-item.util';
import { deleteCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/delete-command-menu-item.util';
import { findCommandMenuItems } from 'test/integration/metadata/suites/command-menu-item/utils/find-command-menu-items.util';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagKey } from 'twenty-shared/types';
describe('CommandMenuItem deletion should succeed', () => {
beforeAll(async () => {
@@ -5,8 +5,8 @@ import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-
import { updateCommandMenuItem } from 'test/integration/metadata/suites/command-menu-item/utils/update-command-menu-item.util';
import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
import { FeatureFlagKey } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
describe('CommandMenuItem update should succeed', () => {
@@ -12,10 +12,10 @@ import {
import {
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
FeatureFlagKey,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type UpsertRowLevelPermissionPredicatesInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input';
type TestSetup = {
@@ -7,10 +7,10 @@ import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util
import {
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
FeatureFlagKey,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type UpsertRowLevelPermissionPredicatesInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input';
describe('Row Level Permission Predicate upsert should succeed', () => {
@@ -2,8 +2,8 @@ import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/m
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type FeatureFlagKey } from 'twenty-shared/types';
import { type FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
export const updateFeatureFlag = async ({