[COMMAND MENU ITEMS] Add engine component key (#18554)

## PR Description

In the process of migrating all the existing commands to the backend, we
stumbled across a couple of problems that made us reconsider the full
migration. This PR introduces a way for command menu items to bypass
front components and to directly reference a frontend component from
twenty front.

It:
- Introduces a `engineFrontComponentKey` field on `CommandMenuItem` as
an alternative to `frontComponentId` and `workflowVersionId`, allowing
command menu items to reference frontend components by key directly
rather than requiring a FrontComponent entity
- Updates the DB constraint to allow exactly one of `workflowVersionId`,
`frontComponentId`, or `engineFrontComponentKey`

### All standard command menu items from the frontend which use
`standardFrontComponentKey`

These are all commands that execute a GraphQL query or a mutation.
Two mains concerned have been raised that made us go with this
(temporary) architecture instead:
- If those commands are part of the standard application, they can only
alter objects from that application and not custom objects.
- We would need to implement a way to trigger optimistic rendering from
the front components, which might take some time to implement.

List:
- Create new record
- Delete (single record)
- Delete records (multiple)
- Restore record
- Restore records (multiple)
- Permanently destroy record
- Permanently destroy records (multiple)
- Add to favorites
- Remove from favorites
- Merge records
- Duplicate Dashboard
- Save Dashboard
- Save Page Layout
- Activate Workflow
- Deactivate Workflow
- Discard Draft (workflow)
- Test Workflow
- Tidy up workflow
- Duplicate Workflow
- Stop (workflow run)
- Use as draft (workflow version)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2026-03-12 13:14:45 +01:00
committed by GitHub
parent 78473a606a
commit e8f8189167
55 changed files with 857 additions and 1152 deletions
@@ -41,12 +41,21 @@ export class FlatCommandMenuItemValidatorService {
const hasFrontComponentUniversalIdentifier = isDefined(
flatCommandMenuItem.frontComponentUniversalIdentifier,
);
const hasEngineComponentKey = isDefined(
flatCommandMenuItem.engineComponentKey,
);
if (hasWorkflowVersionId === hasFrontComponentUniversalIdentifier) {
const sourceCount = [
hasWorkflowVersionId,
hasFrontComponentUniversalIdentifier,
hasEngineComponentKey,
].filter(Boolean).length;
if (sourceCount !== 1) {
validationResult.errors.push({
code: CommandMenuItemExceptionCode.WORKFLOW_OR_FRONT_COMPONENT_REQUIRED,
message: t`Exactly one of workflowVersionId or frontComponentUniversalIdentifier is required`,
userFriendlyMessage: msg`Exactly one of workflow version or front component is required`,
message: t`Exactly one of workflowVersionId, frontComponentUniversalIdentifier or engineComponentKey is required`,
userFriendlyMessage: msg`Exactly one of workflow version, front component or engine component key is required`,
});
}
@@ -72,7 +72,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
flatFieldMetadataMaps,
flatFrontComponentMaps,
flatViewMaps,
flatViewFieldGroupMaps,
flatViewFieldGroupMaps: _flatViewFieldGroupMaps,
}: {
universalConfiguration: FlatPageLayoutWidget['universalConfiguration'];
flatFieldMetadataMaps: MetadataFlatEntityMaps<'fieldMetadata'>;