fix(command-menu-item): persist overrides after save and add reset-to-default (#21623)
## Context Command menu items are an overridable entity (like page-layout / FIELDS widgets), but the override flow in layout-customization mode was broken: - **Move / pin-unpin / hide-label didn't persist.** `useSaveCommandMenuItemsDraft` fired the `updateCommandMenuItem` mutations (backend persisted correctly) but never wrote the result back into `metadataStoreState`, the source the live menu and edit panel read from. So the UI reverted on exit and changes only showed after a hard reload. - **No true "reset to default".** Existing reset controls only reverted the draft to the last-saved values (which still contained overrides). there was no way to clear overrides back to the original values after a save. Notes - removed the footer "Reset to default" button. This is not clear to me how we want to build, let's re-implement better in the next version - reset are done on click and not delayed on the save. This is similar to other reset to default on page layouts where we actually usually reload the component and this is because the FE has no idea what's original VS override from the response itself <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21623?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+3
@@ -8,6 +8,7 @@ export enum CommandMenuItemExceptionCode {
|
||||
COMMAND_MENU_ITEM_NOT_FOUND = 'COMMAND_MENU_ITEM_NOT_FOUND',
|
||||
INVALID_COMMAND_MENU_ITEM_INPUT = 'INVALID_COMMAND_MENU_ITEM_INPUT',
|
||||
WORKFLOW_OR_FRONT_COMPONENT_REQUIRED = 'WORKFLOW_OR_FRONT_COMPONENT_REQUIRED',
|
||||
COMMAND_MENU_ITEM_CANNOT_BE_RESET = 'COMMAND_MENU_ITEM_CANNOT_BE_RESET',
|
||||
}
|
||||
|
||||
const getCommandMenuItemExceptionUserFriendlyMessage = (
|
||||
@@ -20,6 +21,8 @@ const getCommandMenuItemExceptionUserFriendlyMessage = (
|
||||
return msg`Invalid command menu item input.`;
|
||||
case CommandMenuItemExceptionCode.WORKFLOW_OR_FRONT_COMPONENT_REQUIRED:
|
||||
return msg`Either workflow version or front component is required.`;
|
||||
case CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_CANNOT_BE_RESET:
|
||||
return msg`Custom command menu item cannot be reset to default.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
|
||||
+9
@@ -136,6 +136,15 @@ export class CommandMenuItemResolver {
|
||||
return await this.commandMenuItemService.update(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => CommandMenuItemDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async resetCommandMenuItem(
|
||||
@Args('id', { type: () => UUIDScalarType }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<CommandMenuItemDTO> {
|
||||
return await this.commandMenuItemService.reset(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => CommandMenuItemDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async deleteCommandMenuItem(
|
||||
|
||||
+84
@@ -245,6 +245,90 @@ export class CommandMenuItemService {
|
||||
);
|
||||
}
|
||||
|
||||
async reset(id: string, workspaceId: string): Promise<CommandMenuItemDTO> {
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const { flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingFlatCommandMenuItem = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: existingFlatCommandMenuItemMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingFlatCommandMenuItem)) {
|
||||
throw new CommandMenuItemException(
|
||||
'Command menu item not found',
|
||||
CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
existingFlatCommandMenuItem.applicationUniversalIdentifier ===
|
||||
workspaceCustomFlatApplication.universalIdentifier
|
||||
) {
|
||||
throw new CommandMenuItemException(
|
||||
'Custom command menu item cannot be reset to default',
|
||||
CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_CANNOT_BE_RESET,
|
||||
);
|
||||
}
|
||||
|
||||
const flatCommandMenuItemToUpdate: FlatCommandMenuItem = {
|
||||
...existingFlatCommandMenuItem,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatCommandMenuItemToUpdate],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while resetting command menu item to default',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatCommandMenuItemMaps: recomputedFlatCommandMenuItemMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatCommandMenuItemMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatCommandMenuItemToCommandMenuItemDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatCommandMenuItemMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<CommandMenuItemDTO> {
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ export const commandMenuItemGraphqlApiExceptionHandler = (error: Error) => {
|
||||
throw new NotFoundError(error);
|
||||
case CommandMenuItemExceptionCode.INVALID_COMMAND_MENU_ITEM_INPUT:
|
||||
case CommandMenuItemExceptionCode.WORKFLOW_OR_FRONT_COMPONENT_REQUIRED:
|
||||
case CommandMenuItemExceptionCode.COMMAND_MENU_ITEM_CANNOT_BE_RESET:
|
||||
throw new UserInputError(error);
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
|
||||
Reference in New Issue
Block a user