[COMMAND MENU ITEMS] Create union type for command menu item payload (#19432)

Replace generic JSON scalar with a typed GraphQL union
CommandMenuItemPayload (PathNavigationPayload |
ObjectMetadataNavigationPayload) for the CommandMenuItem.payload field
This commit is contained in:
Raphaël Bosi
2026-04-08 14:47:34 +02:00
committed by GitHub
parent 265d859c6e
commit d07c27a907
28 changed files with 673 additions and 305 deletions
@@ -1,7 +1,7 @@
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
import { isObjectMetadataNavigationPayload } from '@/command-menu-item/engine-command/utils/isObjectMetadataNavigationPayload';
import { isPathNavigationPayload } from '@/command-menu-item/engine-command/utils/isPathNavigationPayload';
import { isObjectMetadataCommandMenuItemPayload } from '@/command-menu-item/engine-command/utils/isObjectMetadataCommandMenuItemPayload';
import { isPathCommandMenuItemPayload } from '@/command-menu-item/engine-command/utils/isPathCommandMenuItemPayload';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useNavigate } from 'react-router-dom';
import { AppPath } from 'twenty-shared/types';
@@ -17,11 +17,9 @@ export const NavigationEngineCommand = () => {
return;
}
const navigationPayload = payload;
if (isObjectMetadataNavigationPayload(navigationPayload)) {
if (isObjectMetadataCommandMenuItemPayload(payload)) {
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === navigationPayload.objectMetadataItemId,
(item) => item.id === payload.objectMetadataItemId,
);
if (!isDefined(objectMetadataItem)) {
@@ -38,9 +36,9 @@ export const NavigationEngineCommand = () => {
return;
}
if (isPathNavigationPayload(navigationPayload)) {
if (isPathCommandMenuItemPayload(payload)) {
// eslint-disable-next-line twenty/no-navigate-prefer-link
navigate(navigationPayload.path);
navigate(payload.path);
}
};
@@ -8,6 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
import {
type CommandMenuItemAvailabilityType,
type EngineComponentKey,
type CommandMenuItemPayload,
} from '~/generated-metadata/graphql';
type MountCommandParams = {
@@ -18,7 +19,7 @@ type MountCommandParams = {
workflowVersionId?: string;
availabilityType?: CommandMenuItemAvailabilityType;
availabilityObjectMetadataId?: string | null;
payload?: Record<string, unknown> | null;
payload?: CommandMenuItemPayload | null;
};
export const useMountCommand = () => {
@@ -5,7 +5,10 @@ import {
type Nullable,
type RecordGqlOperationFilter,
} from 'twenty-shared/types';
import { type EngineComponentKey } from '~/generated-metadata/graphql';
import {
type EngineComponentKey,
type CommandMenuItemPayload,
} from '~/generated-metadata/graphql';
export type HeadlessEngineCommandContextApi = {
engineComponentKey: EngineComponentKey;
@@ -16,7 +19,7 @@ export type HeadlessEngineCommandContextApi = {
targetedRecordsRule: ContextStoreTargetedRecordsRule;
selectedRecords: ObjectRecord[];
graphqlFilter: Nullable<RecordGqlOperationFilter>;
payload: Nullable<Record<string, unknown>>;
payload: Nullable<CommandMenuItemPayload>;
};
export type HeadlessFrontComponentCommandContextApi =
@@ -0,0 +1,22 @@
import { isObjectMetadataCommandMenuItemPayload } from '@/command-menu-item/engine-command/utils/isObjectMetadataCommandMenuItemPayload';
import { type CommandMenuItemPayload } from '~/generated-metadata/graphql';
describe('isObjectMetadataCommandMenuItemPayload', () => {
it('should return true for an ObjectMetadataCommandMenuItemPayload', () => {
const payload: CommandMenuItemPayload = {
__typename: 'ObjectMetadataCommandMenuItemPayload',
objectMetadataItemId: 'some-uuid',
};
expect(isObjectMetadataCommandMenuItemPayload(payload)).toBe(true);
});
it('should return false for a PathCommandMenuItemPayload', () => {
const payload: CommandMenuItemPayload = {
__typename: 'PathCommandMenuItemPayload',
path: '/settings',
};
expect(isObjectMetadataCommandMenuItemPayload(payload)).toBe(false);
});
});
@@ -0,0 +1,22 @@
import { isPathCommandMenuItemPayload } from '@/command-menu-item/engine-command/utils/isPathCommandMenuItemPayload';
import { type CommandMenuItemPayload } from '~/generated-metadata/graphql';
describe('isPathCommandMenuItemPayload', () => {
it('should return true for a PathCommandMenuItemPayload', () => {
const payload: CommandMenuItemPayload = {
__typename: 'PathCommandMenuItemPayload',
path: '/settings',
};
expect(isPathCommandMenuItemPayload(payload)).toBe(true);
});
it('should return false for an ObjectMetadataCommandMenuItemPayload', () => {
const payload: CommandMenuItemPayload = {
__typename: 'ObjectMetadataCommandMenuItemPayload',
objectMetadataItemId: 'some-uuid',
};
expect(isPathCommandMenuItemPayload(payload)).toBe(false);
});
});
@@ -14,7 +14,10 @@ import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/Enriche
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { isDefined } from 'twenty-shared/utils';
import { type EngineComponentKey } from '~/generated-metadata/graphql';
import {
type EngineComponentKey,
type CommandMenuItemPayload,
} from '~/generated-metadata/graphql';
export const buildHeadlessCommandContextApi = ({
store,
@@ -25,7 +28,7 @@ export const buildHeadlessCommandContextApi = ({
store: Store;
contextStoreInstanceId: string;
engineComponentKey: EngineComponentKey;
payload?: Record<string, unknown> | null;
payload?: CommandMenuItemPayload | null;
}): HeadlessEngineCommandContextApi => {
const objectMetadataItemId = store.get(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
@@ -0,0 +1,9 @@
import {
type CommandMenuItemPayload,
type ObjectMetadataCommandMenuItemPayload,
} from '~/generated-metadata/graphql';
export const isObjectMetadataCommandMenuItemPayload = (
payload: CommandMenuItemPayload,
): payload is ObjectMetadataCommandMenuItemPayload =>
payload.__typename === 'ObjectMetadataCommandMenuItemPayload';
@@ -1,5 +0,0 @@
export const isObjectMetadataNavigationPayload = (
payload: Record<string, unknown>,
): payload is { objectMetadataItemId: string } =>
'objectMetadataItemId' in payload &&
typeof payload.objectMetadataItemId === 'string';
@@ -0,0 +1,9 @@
import {
type CommandMenuItemPayload,
type PathCommandMenuItemPayload,
} from '~/generated-metadata/graphql';
export const isPathCommandMenuItemPayload = (
payload: CommandMenuItemPayload,
): payload is PathCommandMenuItemPayload =>
payload.__typename === 'PathCommandMenuItemPayload';
@@ -1,4 +0,0 @@
export const isPathNavigationPayload = (
payload: Record<string, unknown>,
): payload is { path: string } =>
'path' in payload && typeof payload.path === 'string';