Add dynamic create record commands
This commit is contained in:
+5
-3
@@ -3,7 +3,7 @@ import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command
|
||||
import { CreateNewIndexRecordNoSelectionRecordCommand } from '@/command-menu-item/engine-command/record/no-selection/components/CreateNewIndexRecordNoSelectionRecordCommand';
|
||||
import { isObjectMetadataCommandMenuItemPayload } from '@/command-menu-item/engine-command/utils/isObjectMetadataCommandMenuItemPayload';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType';
|
||||
import { getObjectColorWithFallback } from '@/object-metadata/utils/getObjectColorWithFallback';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { createRecordObjectMetadataItemIdComponentState } from '@/side-panel/pages/create-record/states/createRecordObjectMetadataItemIdComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@@ -11,7 +11,7 @@ import { useStore } from 'jotai';
|
||||
import { isObjectMetadataManuallyCreatable } from 'twenty-shared/metadata';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { getIconTileColorShades, useIcons } from 'twenty-ui/display';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const CreateNewRecordCommand = () => {
|
||||
@@ -58,7 +58,9 @@ export const CreateNewRecordCommand = () => {
|
||||
page: SidePanelPages.CreateRecord,
|
||||
pageTitle: t`New ${objectMetadataItem.labelSingular}`,
|
||||
pageIcon: Icon,
|
||||
pageIconColor: getIconColorForObjectType(objectMetadataItem.nameSingular),
|
||||
pageIconColor: getIconTileColorShades(
|
||||
getObjectColorWithFallback(objectMetadataItem),
|
||||
).iconColor,
|
||||
pageId: pageComponentInstanceId,
|
||||
});
|
||||
};
|
||||
|
||||
+6
-3
@@ -20,6 +20,10 @@ import {
|
||||
responseData as findManyObjectMetadataItemsResponseData,
|
||||
} from '@/object-metadata/hooks/__mocks__/useFindManyObjectMetadataItems';
|
||||
|
||||
const findManyObjectMetadataItemsResult = jest.fn(() => ({
|
||||
data: findManyObjectMetadataItemsResponseData,
|
||||
}));
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
@@ -48,9 +52,7 @@ const mocks = [
|
||||
query: findManyObjectMetadataItemsQuery,
|
||||
variables: {},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: findManyObjectMetadataItemsResponseData,
|
||||
})),
|
||||
result: findManyObjectMetadataItemsResult,
|
||||
},
|
||||
{
|
||||
request: {
|
||||
@@ -109,6 +111,7 @@ describe('useCreateOneObjectMetadataItem', () => {
|
||||
});
|
||||
jestExpectSuccessfulMetadataRequestResult(res);
|
||||
expect(res.response).toEqual({ data: { createOneObject: responseData } });
|
||||
expect(findManyObjectMetadataItemsResult).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+39
-18
@@ -5,13 +5,16 @@ import {
|
||||
FindManyCommandMenuItemsDocument,
|
||||
FindManyNavigationMenuItemsDocument,
|
||||
FindManyViewsDocument,
|
||||
type ObjectMetadataItemsQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
|
||||
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
|
||||
import { type FlatFieldMetadataItem } from '@/metadata-store/types/FlatFieldMetadataItem';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { splitObjectMetadataGqlResponse } from '@/metadata-store/utils/splitObjectMetadataGqlResponse';
|
||||
import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
|
||||
@@ -60,7 +63,7 @@ export const useCreateOneObjectMetadataItem = () => {
|
||||
items: [objectData as FlatObjectMetadataItem],
|
||||
});
|
||||
|
||||
const flatFields = fieldsList.map((field) => {
|
||||
const createdObjectFlatFields = fieldsList.map((field) => {
|
||||
const { __typename: _fieldTypename, ...fieldData } = field;
|
||||
|
||||
return {
|
||||
@@ -69,29 +72,47 @@ export const useCreateOneObjectMetadataItem = () => {
|
||||
} as FlatFieldMetadataItem;
|
||||
});
|
||||
|
||||
addToDraft({ key: 'fieldMetadataItems', items: flatFields });
|
||||
addToDraft({
|
||||
key: 'fieldMetadataItems',
|
||||
items: createdObjectFlatFields,
|
||||
});
|
||||
|
||||
applyChanges();
|
||||
|
||||
const [viewsResult, navItemsResult, commandMenuItemsResult] =
|
||||
await Promise.all([
|
||||
client.query({
|
||||
query: FindManyViewsDocument,
|
||||
variables: { objectMetadataId: createdObject.id },
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
client.query({
|
||||
query: FindManyNavigationMenuItemsDocument,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
client.query({
|
||||
query: FindManyCommandMenuItemsDocument,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
]);
|
||||
const [
|
||||
objectMetadataItemsResult,
|
||||
viewsResult,
|
||||
navItemsResult,
|
||||
commandMenuItemsResult,
|
||||
] = await Promise.all([
|
||||
client.query<ObjectMetadataItemsQuery>({
|
||||
query: FIND_MANY_OBJECT_METADATA_ITEMS,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
client.query({
|
||||
query: FindManyViewsDocument,
|
||||
variables: { objectMetadataId: createdObject.id },
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
client.query({
|
||||
query: FindManyNavigationMenuItemsDocument,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
client.query({
|
||||
query: FindManyCommandMenuItemsDocument,
|
||||
fetchPolicy: 'network-only',
|
||||
}),
|
||||
]);
|
||||
|
||||
const fetchedViews = viewsResult.data?.getViews ?? [];
|
||||
|
||||
const { flatObjects, flatFields, flatIndexes } =
|
||||
splitObjectMetadataGqlResponse(objectMetadataItemsResult.data);
|
||||
|
||||
replaceDraft('objectMetadataItems', flatObjects);
|
||||
replaceDraft('fieldMetadataItems', flatFields);
|
||||
replaceDraft('indexMetadataItems', flatIndexes);
|
||||
|
||||
const {
|
||||
flatViews,
|
||||
flatViewFields,
|
||||
|
||||
@@ -120,6 +120,15 @@ export const SidePanelPageInfo = ({ pageChip }: SidePanelPageInfoProps) => {
|
||||
return <SidePanelAskAiInfo />;
|
||||
}
|
||||
|
||||
if (pageChip.page?.page === SidePanelPages.CreateRecord) {
|
||||
return (
|
||||
<SidePanelPageInfoLayout
|
||||
icon={pageChip.Icons[0]}
|
||||
title={<OverflowingTextWithTooltip text={pageChip.text ?? ''} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (pageChip.page?.page === SidePanelPages.NavigationMenuAddItem) {
|
||||
return (
|
||||
<SidePanelPageInfoLayout
|
||||
|
||||
@@ -55,6 +55,11 @@ export const useSidePanelContextChips = () => {
|
||||
(page) => page.page !== SidePanelPages.CommandMenuDisplay,
|
||||
);
|
||||
|
||||
const getPageIconColor = (pageIconColor: string | undefined) =>
|
||||
isDefined(pageIconColor) && pageIconColor !== 'currentColor'
|
||||
? pageIconColor
|
||||
: themeCssVariables.font.color.tertiary;
|
||||
|
||||
return filteredSidePanelNavigationStack
|
||||
.map((page, index) => {
|
||||
const isLastChip =
|
||||
@@ -109,17 +114,17 @@ export const useSidePanelContextChips = () => {
|
||||
return {
|
||||
page,
|
||||
Icons: isLastChip
|
||||
? [<page.pageIcon size={iconSizeSm} />]
|
||||
? [
|
||||
<page.pageIcon
|
||||
size={iconSizeSm}
|
||||
color={getPageIconColor(page.pageIconColor)}
|
||||
/>,
|
||||
]
|
||||
: [
|
||||
<SidePanelContextChipIconWrapper>
|
||||
<page.pageIcon
|
||||
size={iconSizeSm}
|
||||
color={
|
||||
isDefined(page.pageIconColor) &&
|
||||
page.pageIconColor !== 'currentColor'
|
||||
? page.pageIconColor
|
||||
: themeCssVariables.font.color.tertiary
|
||||
}
|
||||
color={getPageIconColor(page.pageIconColor)}
|
||||
/>
|
||||
</SidePanelContextChipIconWrapper>,
|
||||
],
|
||||
|
||||
+18
-11
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { type KeyboardEvent, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
@@ -8,17 +8,14 @@ import { FormFieldInput } from '@/object-record/record-field/ui/components/FormF
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { isUpdateRecordValueEmpty } from '@/object-record/record-update-multiple/utils/isUpdateRecordValueEmpty';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
|
||||
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
|
||||
import { createRecordObjectMetadataItemIdComponentState } from '@/side-panel/pages/create-record/states/createRecordObjectMetadataItemIdComponentState';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useViewOrDefaultView } from '@/views/hooks/useViewOrDefaultView';
|
||||
import { shouldDisplayFormField } from '@/workflow/workflow-steps/workflow-actions/utils/shouldDisplayFormField';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { isObjectMetadataManuallyCreatable } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
@@ -200,19 +197,29 @@ const SidePanelCreateRecordForm = ({
|
||||
t,
|
||||
]);
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
|
||||
callback: () => {
|
||||
const handleFormKeyDown = useCallback(
|
||||
(event: KeyboardEvent<HTMLDivElement>) => {
|
||||
const isSubmitShortcut =
|
||||
(event.metaKey || event.ctrlKey) &&
|
||||
(event.key === 'Enter' || event.code === 'NumpadEnter');
|
||||
|
||||
if (!isSubmitShortcut) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
if (!isSubmitting) {
|
||||
handleSave();
|
||||
}
|
||||
},
|
||||
focusId: SIDE_PANEL_FOCUS_ID,
|
||||
dependencies: [handleSave, isSubmitting],
|
||||
});
|
||||
[handleSave, isSubmitting],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledPage>
|
||||
<StyledPage onKeyDownCapture={handleFormKeyDown}>
|
||||
<StyledSectionContainer>
|
||||
<Section>
|
||||
{fieldsWithDefinitions.map(
|
||||
|
||||
+5
-6
@@ -2,11 +2,10 @@ import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
const COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT =
|
||||
'CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE';
|
||||
|
||||
const CREATE_RECORD_PAYLOAD_CONSTRAINT_SQL = `("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'CREATE_NEW_RECORD' AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION', 'CREATE_NEW_RECORD') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL)`;
|
||||
import {
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT,
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL,
|
||||
} from 'src/engine/metadata-modules/command-menu-item/constants/command-menu-item-engine-key-coherence-constraint-sql.constant';
|
||||
|
||||
const LEGACY_PAYLOAD_CONSTRAINT_SQL = `("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL)`;
|
||||
|
||||
@@ -20,7 +19,7 @@ export class AllowCreateRecordCommandMenuItemPayloadFastInstanceCommand
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "${COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT}" CHECK (${CREATE_RECORD_PAYLOAD_CONSTRAINT_SQL})`,
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "${COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT}" CHECK (${COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL})`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+9
-19
@@ -11,6 +11,7 @@ import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/deco
|
||||
import {
|
||||
CREATE_RECORD_COMMAND_UUID_NAMESPACE,
|
||||
buildCreateRecordFlatCommandMenuItem,
|
||||
buildUpdatedCreateRecordFlatCommandMenuItem,
|
||||
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-create-record-flat-command-menu-item.util';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { seedCompareObjectMetadataForNavigationPosition } from 'src/engine/metadata-modules/flat-command-menu-item/utils/seed-compare-object-metadata-for-navigation-position.util';
|
||||
@@ -117,26 +118,15 @@ export class BackfillCreateRecordCommandMenuItemsCommand extends ActiveOrSuspend
|
||||
});
|
||||
|
||||
if (isDefined(existingCreateCommand)) {
|
||||
if (
|
||||
existingCreateCommand.label !== expectedCreateCommand.label ||
|
||||
existingCreateCommand.shortLabel !==
|
||||
expectedCreateCommand.shortLabel ||
|
||||
existingCreateCommand.icon !== expectedCreateCommand.icon ||
|
||||
existingCreateCommand.conditionalAvailabilityExpression !==
|
||||
expectedCreateCommand.conditionalAvailabilityExpression ||
|
||||
JSON.stringify(existingCreateCommand.payload) !==
|
||||
JSON.stringify(expectedCreateCommand.payload)
|
||||
) {
|
||||
commandMenuItemsToUpdate.push({
|
||||
...existingCreateCommand,
|
||||
label: expectedCreateCommand.label,
|
||||
shortLabel: expectedCreateCommand.shortLabel,
|
||||
icon: expectedCreateCommand.icon,
|
||||
conditionalAvailabilityExpression:
|
||||
expectedCreateCommand.conditionalAvailabilityExpression,
|
||||
payload: expectedCreateCommand.payload,
|
||||
updatedAt: now,
|
||||
const updatedCreateCommand =
|
||||
buildUpdatedCreateRecordFlatCommandMenuItem({
|
||||
existingCommandMenuItem: existingCreateCommand,
|
||||
objectMetadata: flatObjectMetadata,
|
||||
now,
|
||||
});
|
||||
|
||||
if (isDefined(updatedCreateCommand)) {
|
||||
commandMenuItemsToUpdate.push(updatedCreateCommand);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
+7
-2
@@ -1,13 +1,18 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import {
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT,
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL,
|
||||
} from 'src/engine/metadata-modules/command-menu-item/constants/command-menu-item-engine-key-coherence-constraint-sql.constant';
|
||||
|
||||
export const addPayloadCheckConstraintToCommandMenuItem = async (
|
||||
queryRunner: QueryRunner,
|
||||
): Promise<void> => {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT IF EXISTS "CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE"`,
|
||||
`ALTER TABLE "core"."commandMenuItem" DROP CONSTRAINT IF EXISTS "${COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT}"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE" CHECK (("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'CREATE_NEW_RECORD' AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION', 'CREATE_NEW_RECORD') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL))`,
|
||||
`ALTER TABLE "core"."commandMenuItem" ADD CONSTRAINT "${COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT}" CHECK (${COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL})`,
|
||||
);
|
||||
};
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL } from 'src/engine/metadata-modules/command-menu-item/constants/command-menu-item-engine-key-coherence-constraint-sql.constant';
|
||||
|
||||
describe('COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL', () => {
|
||||
it('constrains CREATE_NEW_RECORD payloads to null or object metadata payloads', () => {
|
||||
expect(COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL).toContain(
|
||||
`"engineComponentKey" = 'CREATE_NEW_RECORD'`,
|
||||
);
|
||||
expect(COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL).toContain(
|
||||
`"payload" IS NULL OR`,
|
||||
);
|
||||
expect(COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL).toContain(
|
||||
`"payload" ? 'objectMetadataItemId'`,
|
||||
);
|
||||
expect(COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL).toContain(
|
||||
`"payload" ->> 'objectMetadataItemId' <> ''`,
|
||||
);
|
||||
expect(COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL).toContain(
|
||||
`NOT ("payload" ? 'path')`,
|
||||
);
|
||||
});
|
||||
});
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export const COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT =
|
||||
'CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE';
|
||||
|
||||
const CREATE_NEW_RECORD_PAYLOAD_SQL = `("payload" IS NULL OR (jsonb_typeof("payload") = 'object' AND "payload" ? 'objectMetadataItemId' AND jsonb_typeof("payload" -> 'objectMetadataItemId') = 'string' AND "payload" ->> 'objectMetadataItemId' <> '' AND NOT ("payload" ? 'path')))`;
|
||||
|
||||
export const COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL = `("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'CREATE_NEW_RECORD' AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND ${CREATE_NEW_RECORD_PAYLOAD_SQL}) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION', 'CREATE_NEW_RECORD') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL)`;
|
||||
+6
-2
@@ -11,6 +11,10 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import {
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT,
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL,
|
||||
} from 'src/engine/metadata-modules/command-menu-item/constants/command-menu-item-engine-key-coherence-constraint-sql.constant';
|
||||
import { type CommandMenuItemPayload } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item-payload.union';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
|
||||
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
|
||||
@@ -36,8 +40,8 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
|
||||
'workspaceId',
|
||||
])
|
||||
@Check(
|
||||
'CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE',
|
||||
`("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'CREATE_NEW_RECORD' AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION', 'CREATE_NEW_RECORD') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL)`,
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT,
|
||||
COMMAND_MENU_ITEM_ENGINE_KEY_COHERENCE_CONSTRAINT_SQL,
|
||||
)
|
||||
export class CommandMenuItemEntity
|
||||
extends SyncableEntity
|
||||
|
||||
+65
@@ -8,6 +8,7 @@ import {
|
||||
CREATE_RECORD_INTERPOLATED_LABEL,
|
||||
CREATE_RECORD_INTERPOLATED_SHORT_LABEL,
|
||||
buildCreateRecordFlatCommandMenuItem,
|
||||
buildUpdatedCreateRecordFlatCommandMenuItem,
|
||||
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-create-record-flat-command-menu-item.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
@@ -60,4 +61,68 @@ describe('buildCreateRecordFlatCommandMenuItem', () => {
|
||||
updatedAt: '2026-05-22T00:00:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
it('updates stale create command metadata while preserving stable fields', () => {
|
||||
const existingCommandMenuItem = buildCreateRecordFlatCommandMenuItem({
|
||||
objectMetadata: {
|
||||
id: 'object-metadata-id',
|
||||
universalIdentifier: '5da6fdc9-48db-4cd1-b41c-907d8edaaf7b',
|
||||
nameSingular: 'company',
|
||||
},
|
||||
commandMenuItemId: 'command-menu-item-id',
|
||||
applicationId: 'application-id',
|
||||
workspaceId: 'workspace-id',
|
||||
position: 42,
|
||||
now: '2026-05-22T00:00:00.000Z',
|
||||
});
|
||||
|
||||
const result = buildUpdatedCreateRecordFlatCommandMenuItem({
|
||||
existingCommandMenuItem,
|
||||
objectMetadata: {
|
||||
id: 'object-metadata-id',
|
||||
universalIdentifier: '5da6fdc9-48db-4cd1-b41c-907d8edaaf7b',
|
||||
nameSingular: 'organization',
|
||||
},
|
||||
now: '2026-05-23T00:00:00.000Z',
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
id: 'command-menu-item-id',
|
||||
applicationId: 'application-id',
|
||||
workspaceId: 'workspace-id',
|
||||
position: 42,
|
||||
conditionalAvailabilityExpression:
|
||||
'targetObjectWritePermissions.organization and not (pageType == "INDEX_PAGE" and objectMetadataItem.nameSingular == "organization")',
|
||||
payload: { objectMetadataItemId: 'object-metadata-id' },
|
||||
createdAt: '2026-05-22T00:00:00.000Z',
|
||||
updatedAt: '2026-05-23T00:00:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update an already current create command', () => {
|
||||
const existingCommandMenuItem = buildCreateRecordFlatCommandMenuItem({
|
||||
objectMetadata: {
|
||||
id: 'object-metadata-id',
|
||||
universalIdentifier: '5da6fdc9-48db-4cd1-b41c-907d8edaaf7b',
|
||||
nameSingular: 'company',
|
||||
},
|
||||
commandMenuItemId: 'command-menu-item-id',
|
||||
applicationId: 'application-id',
|
||||
workspaceId: 'workspace-id',
|
||||
position: 42,
|
||||
now: '2026-05-22T00:00:00.000Z',
|
||||
});
|
||||
|
||||
const result = buildUpdatedCreateRecordFlatCommandMenuItem({
|
||||
existingCommandMenuItem,
|
||||
objectMetadata: {
|
||||
id: 'object-metadata-id',
|
||||
universalIdentifier: '5da6fdc9-48db-4cd1-b41c-907d8edaaf7b',
|
||||
nameSingular: 'company',
|
||||
},
|
||||
now: '2026-05-23T00:00:00.000Z',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
+46
@@ -1,3 +1,4 @@
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { v5 } from 'uuid';
|
||||
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
|
||||
@@ -67,3 +68,48 @@ export const buildCreateRecordFlatCommandMenuItem = ({
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
|
||||
export const buildUpdatedCreateRecordFlatCommandMenuItem = ({
|
||||
existingCommandMenuItem,
|
||||
objectMetadata,
|
||||
now,
|
||||
}: {
|
||||
existingCommandMenuItem: FlatCommandMenuItem;
|
||||
objectMetadata: {
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
nameSingular: string;
|
||||
};
|
||||
now: string;
|
||||
}): FlatCommandMenuItem | undefined => {
|
||||
const expectedCommandMenuItem = buildCreateRecordFlatCommandMenuItem({
|
||||
objectMetadata,
|
||||
commandMenuItemId: existingCommandMenuItem.id,
|
||||
applicationId: existingCommandMenuItem.applicationId,
|
||||
workspaceId: existingCommandMenuItem.workspaceId,
|
||||
position: existingCommandMenuItem.position,
|
||||
now,
|
||||
});
|
||||
|
||||
if (
|
||||
existingCommandMenuItem.label === expectedCommandMenuItem.label &&
|
||||
existingCommandMenuItem.shortLabel === expectedCommandMenuItem.shortLabel &&
|
||||
existingCommandMenuItem.icon === expectedCommandMenuItem.icon &&
|
||||
existingCommandMenuItem.conditionalAvailabilityExpression ===
|
||||
expectedCommandMenuItem.conditionalAvailabilityExpression &&
|
||||
isEqual(existingCommandMenuItem.payload, expectedCommandMenuItem.payload)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...existingCommandMenuItem,
|
||||
label: expectedCommandMenuItem.label,
|
||||
shortLabel: expectedCommandMenuItem.shortLabel,
|
||||
icon: expectedCommandMenuItem.icon,
|
||||
conditionalAvailabilityExpression:
|
||||
expectedCommandMenuItem.conditionalAvailabilityExpression,
|
||||
payload: expectedCommandMenuItem.payload,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
|
||||
+118
-106
@@ -18,6 +18,7 @@ import { type FlatApplication } from 'src/engine/core-modules/application/types/
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import {
|
||||
buildCreateRecordFlatCommandMenuItem,
|
||||
buildUpdatedCreateRecordFlatCommandMenuItem,
|
||||
CREATE_RECORD_COMMAND_UUID_NAMESPACE,
|
||||
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-create-record-flat-command-menu-item.util';
|
||||
import {
|
||||
@@ -129,30 +130,20 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatEntityId: updateObjectInput.id,
|
||||
});
|
||||
|
||||
const {
|
||||
commandMenuItemsToCreate,
|
||||
commandMenuItemsToDelete,
|
||||
commandMenuItemsToUpdate,
|
||||
} = this.computeCommandMenuItemChangesForObjectUpdate({
|
||||
existingFlatObjectMetadata,
|
||||
flatObjectMetadataToUpdate,
|
||||
flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps,
|
||||
workspaceId,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const isActiveChangeDefined = isDefined(updateObjectInput.update.isActive);
|
||||
|
||||
const isBeingEnabled =
|
||||
isActiveChangeDefined &&
|
||||
updateObjectInput.update.isActive === true &&
|
||||
isDefined(existingFlatObjectMetadata) &&
|
||||
!existingFlatObjectMetadata.isActive;
|
||||
|
||||
const isBeingDisabled =
|
||||
isActiveChangeDefined &&
|
||||
updateObjectInput.update.isActive === false &&
|
||||
isDefined(existingFlatObjectMetadata) &&
|
||||
existingFlatObjectMetadata.isActive;
|
||||
|
||||
const { commandMenuItemsToCreate, commandMenuItemsToDelete } =
|
||||
this.computeCommandMenuItemChangesForActiveToggle({
|
||||
isBeingEnabled,
|
||||
isBeingDisabled,
|
||||
existingFlatObjectMetadata,
|
||||
flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps,
|
||||
workspaceId,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
@@ -197,7 +188,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
|
||||
const hasCommandMenuItemChanges =
|
||||
commandMenuItemsToCreate.length > 0 ||
|
||||
commandMenuItemsToDelete.length > 0;
|
||||
commandMenuItemsToDelete.length > 0 ||
|
||||
commandMenuItemsToUpdate.length > 0;
|
||||
|
||||
if (hasCommandMenuItemChanges) {
|
||||
const commandMenuItemMigrationResult =
|
||||
@@ -207,7 +199,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: commandMenuItemsToCreate,
|
||||
flatEntityToDelete: commandMenuItemsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
flatEntityToUpdate: commandMenuItemsToUpdate,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
@@ -250,10 +242,12 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
]);
|
||||
}
|
||||
|
||||
if (isActiveChangeDefined) {
|
||||
if (isActiveChangeDefined || hasCommandMenuItemChanges) {
|
||||
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatNavigationMenuItemMaps', 'flatCommandMenuItemMaps'],
|
||||
flatMapsKeys: isActiveChangeDefined
|
||||
? ['flatNavigationMenuItemMaps', 'flatCommandMenuItemMaps']
|
||||
: ['flatCommandMenuItemMaps'],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1017,17 +1011,23 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
});
|
||||
}
|
||||
|
||||
private computeCommandMenuItemChangesForActiveToggle({
|
||||
isBeingEnabled,
|
||||
isBeingDisabled,
|
||||
private computeCommandMenuItemChangesForObjectUpdate({
|
||||
existingFlatObjectMetadata,
|
||||
flatObjectMetadataToUpdate,
|
||||
flatCommandMenuItemMaps,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
}: {
|
||||
isBeingEnabled: boolean;
|
||||
isBeingDisabled: boolean;
|
||||
existingFlatObjectMetadata: FlatObjectMetadata | undefined;
|
||||
flatObjectMetadataToUpdate: {
|
||||
universalIdentifier: string;
|
||||
labelPlural: string;
|
||||
icon: string | null;
|
||||
nameSingular: string;
|
||||
shortcut: string | null;
|
||||
isActive: boolean;
|
||||
isSystem: boolean;
|
||||
};
|
||||
flatCommandMenuItemMaps: {
|
||||
byUniversalIdentifier: Record<string, FlatCommandMenuItem | undefined>;
|
||||
};
|
||||
@@ -1035,98 +1035,110 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
applicationId: string;
|
||||
}): {
|
||||
commandMenuItemsToCreate: FlatCommandMenuItem[];
|
||||
commandMenuItemsToUpdate: FlatCommandMenuItem[];
|
||||
commandMenuItemsToDelete: FlatCommandMenuItem[];
|
||||
} {
|
||||
if (!isDefined(existingFlatObjectMetadata)) {
|
||||
return { commandMenuItemsToCreate: [], commandMenuItemsToDelete: [] };
|
||||
return {
|
||||
commandMenuItemsToCreate: [],
|
||||
commandMenuItemsToUpdate: [],
|
||||
commandMenuItemsToDelete: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (isBeingEnabled) {
|
||||
const commandMenuItemsToCreate: FlatCommandMenuItem[] = [];
|
||||
let nextPosition = this.computeNextCommandMenuItemPosition(
|
||||
const updatedFlatObjectMetadataWithId = {
|
||||
...flatObjectMetadataToUpdate,
|
||||
id: existingFlatObjectMetadata.id,
|
||||
};
|
||||
|
||||
const existingNavigationCommandMenuItem =
|
||||
this.findNavigationCommandMenuItemForObject({
|
||||
objectUniversalIdentifier:
|
||||
existingFlatObjectMetadata.universalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
const existingCreateRecordCommandMenuItem =
|
||||
this.findCreateRecordCommandMenuItemForObject({
|
||||
objectUniversalIdentifier:
|
||||
existingFlatObjectMetadata.universalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
if (!flatObjectMetadataToUpdate.isActive) {
|
||||
return {
|
||||
commandMenuItemsToCreate: [],
|
||||
commandMenuItemsToUpdate: [],
|
||||
commandMenuItemsToDelete: [
|
||||
existingNavigationCommandMenuItem,
|
||||
existingCreateRecordCommandMenuItem,
|
||||
].filter(isDefined),
|
||||
};
|
||||
}
|
||||
|
||||
const commandMenuItemsToCreate: FlatCommandMenuItem[] = [];
|
||||
const commandMenuItemsToUpdate: FlatCommandMenuItem[] = [];
|
||||
const commandMenuItemsToDelete: FlatCommandMenuItem[] = [];
|
||||
let nextPosition = this.computeNextCommandMenuItemPosition(
|
||||
flatCommandMenuItemMaps,
|
||||
);
|
||||
|
||||
if (!isDefined(existingNavigationCommandMenuItem)) {
|
||||
commandMenuItemsToCreate.push(
|
||||
this.buildFlatNavigationCommandMenuItem({
|
||||
objectMetadata: updatedFlatObjectMetadataWithId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
flatCommandMenuItemMaps,
|
||||
position: nextPosition++,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const existingNavigationCommandMenuItem =
|
||||
this.findNavigationCommandMenuItemForObject({
|
||||
objectUniversalIdentifier:
|
||||
existingFlatObjectMetadata.universalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
});
|
||||
const isManuallyCreatableAfterUpdate = isObjectMetadataManuallyCreatable(
|
||||
flatObjectMetadataToUpdate,
|
||||
);
|
||||
|
||||
if (!isDefined(existingNavigationCommandMenuItem)) {
|
||||
commandMenuItemsToCreate.push(
|
||||
this.buildFlatNavigationCommandMenuItem({
|
||||
objectMetadata: existingFlatObjectMetadata,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
flatCommandMenuItemMaps,
|
||||
position: nextPosition++,
|
||||
}),
|
||||
);
|
||||
if (!isManuallyCreatableAfterUpdate) {
|
||||
if (isDefined(existingCreateRecordCommandMenuItem)) {
|
||||
commandMenuItemsToDelete.push(existingCreateRecordCommandMenuItem);
|
||||
}
|
||||
|
||||
const existingCreateRecordCommandMenuItem =
|
||||
this.findCreateRecordCommandMenuItemForObject({
|
||||
objectUniversalIdentifier:
|
||||
existingFlatObjectMetadata.universalIdentifier,
|
||||
return {
|
||||
commandMenuItemsToCreate,
|
||||
commandMenuItemsToUpdate,
|
||||
commandMenuItemsToDelete,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isDefined(existingCreateRecordCommandMenuItem)) {
|
||||
commandMenuItemsToCreate.push(
|
||||
this.buildFlatCreateRecordCommandMenuItem({
|
||||
objectMetadata: updatedFlatObjectMetadataWithId,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
flatCommandMenuItemMaps,
|
||||
position: nextPosition++,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const updatedCreateRecordCommandMenuItem =
|
||||
buildUpdatedCreateRecordFlatCommandMenuItem({
|
||||
existingCommandMenuItem: existingCreateRecordCommandMenuItem,
|
||||
objectMetadata: updatedFlatObjectMetadataWithId,
|
||||
now: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (
|
||||
isObjectMetadataManuallyCreatable({
|
||||
...existingFlatObjectMetadata,
|
||||
isActive: true,
|
||||
}) &&
|
||||
!isDefined(existingCreateRecordCommandMenuItem)
|
||||
) {
|
||||
commandMenuItemsToCreate.push(
|
||||
this.buildFlatCreateRecordCommandMenuItem({
|
||||
objectMetadata: existingFlatObjectMetadata,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
flatCommandMenuItemMaps,
|
||||
position: nextPosition++,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (commandMenuItemsToCreate.length > 0) {
|
||||
return {
|
||||
commandMenuItemsToCreate,
|
||||
commandMenuItemsToDelete: [],
|
||||
};
|
||||
if (isDefined(updatedCreateRecordCommandMenuItem)) {
|
||||
commandMenuItemsToUpdate.push(updatedCreateRecordCommandMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBeingDisabled) {
|
||||
const navigationCommandMenuItemToDelete =
|
||||
this.findNavigationCommandMenuItemForObject({
|
||||
objectUniversalIdentifier:
|
||||
existingFlatObjectMetadata.universalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
});
|
||||
const createRecordCommandMenuItemToDelete =
|
||||
this.findCreateRecordCommandMenuItemForObject({
|
||||
objectUniversalIdentifier:
|
||||
existingFlatObjectMetadata.universalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
const commandMenuItemsToDelete = [
|
||||
navigationCommandMenuItemToDelete,
|
||||
createRecordCommandMenuItemToDelete,
|
||||
].filter(isDefined);
|
||||
|
||||
if (commandMenuItemsToDelete.length > 0) {
|
||||
return {
|
||||
commandMenuItemsToCreate: [],
|
||||
commandMenuItemsToDelete,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { commandMenuItemsToCreate: [], commandMenuItemsToDelete: [] };
|
||||
return {
|
||||
commandMenuItemsToCreate,
|
||||
commandMenuItemsToUpdate,
|
||||
commandMenuItemsToDelete,
|
||||
};
|
||||
}
|
||||
|
||||
public async findOneWithinWorkspace(
|
||||
|
||||
Reference in New Issue
Block a user