fix: handle select-all (exclusion) mode in delete command conditional availability

https://sonarly.com/issue/21546?type=bug

When users select all records (exclusion mode), the delete option never appears in the command menu because the `noneDefined(selectedRecords, "deletedAt")` check in the conditional availability expression evaluates to `false` on empty arrays, and `selectedRecords` is always `[]` in exclusion mode.

Fix: Modified the `conditionalAvailabilityExpression` for the three bulk record commands (delete, restore, destroy) in `standard-command-menu-item.constant.ts` to handle the "select all" (exclusion) mode.

**Problem:** When "select all" is used, the context store switches to exclusion mode where `selectedRecords` is always `[]` (empty array). The `noneDefined()` and `everyDefined()` functions in the expression parser return `false` for empty arrays by design. This caused the delete/restore/destroy commands to never appear in the command menu during "select all".

**Fix:** Wrapped each `selectedRecords` array check with `(isSelectAll or ...)` so that:
- In **selection mode** (individual records selected): the original `noneDefined`/`everyDefined` checks still apply, verifying each record's `deletedAt` status
- In **exclusion mode** (select all): the `selectedRecords` check is bypassed since individual records aren't available in the context, and the backend handles filtering during the actual operation

The `isSelectAll` boolean is already computed and available in the `commandMenuContextApi` (set in `useCommandMenuContextApi.ts` line 116 as `contextStoreTargetedRecordsRule.mode === 'exclusion'`).

Changes:
1. **deleteRecords**: `noneDefined(selectedRecords, "deletedAt")` → `(isSelectAll or noneDefined(selectedRecords, "deletedAt"))`
2. **restoreRecords**: `everyDefined(selectedRecords, "deletedAt")` → `(isSelectAll or everyDefined(selectedRecords, "deletedAt"))`
3. **destroyRecords**: `everyDefined(selectedRecords, "deletedAt")` → `(isSelectAll or everyDefined(selectedRecords, "deletedAt"))`

Note: For restore/destroy in select-all mode, the commands will appear even though we can't verify all records have `deletedAt` set. This is acceptable because these commands already require `hasAnySoftDeleteFilterOnView` (restore) or the user being in a deleted records view, and the backend filters appropriately during execution. Existing workspaces will need a `workspace:sync-metadata` run to pick up the updated expressions.
This commit is contained in:
Sonarly Claude Code 2026-04-03 13:08:44 +00:00
parent 119014f86d
commit a9d8defccd

View file

@ -59,7 +59,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
shortLabel: 'Delete',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
conditionalAvailabilityExpression:
'numberOfSelectedRecords >= 1 and not hasAnySoftDeleteFilterOnView and objectPermissions.canSoftDeleteObjectRecords and noneDefined(selectedRecords, "deletedAt") and numberOfSelectedRecords < 10000',
'numberOfSelectedRecords >= 1 and not hasAnySoftDeleteFilterOnView and objectPermissions.canSoftDeleteObjectRecords and (isSelectAll or noneDefined(selectedRecords, "deletedAt")) and numberOfSelectedRecords < 10000',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.DELETE_RECORDS,
@ -74,7 +74,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
shortLabel: 'Restore',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
conditionalAvailabilityExpression:
'numberOfSelectedRecords >= 1 and everyDefined(selectedRecords, "deletedAt") and objectPermissions.canSoftDeleteObjectRecords and (pageType == "RECORD_PAGE" or hasAnySoftDeleteFilterOnView) and numberOfSelectedRecords < 10000',
'numberOfSelectedRecords >= 1 and (isSelectAll or everyDefined(selectedRecords, "deletedAt")) and objectPermissions.canSoftDeleteObjectRecords and (pageType == "RECORD_PAGE" or hasAnySoftDeleteFilterOnView) and numberOfSelectedRecords < 10000',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.RESTORE_RECORDS,
@ -89,7 +89,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
shortLabel: 'Destroy',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
conditionalAvailabilityExpression:
'numberOfSelectedRecords >= 1 and objectPermissions.canDestroyObjectRecords and everyDefined(selectedRecords, "deletedAt") and numberOfSelectedRecords < 10000',
'numberOfSelectedRecords >= 1 and objectPermissions.canDestroyObjectRecords and (isSelectAll or everyDefined(selectedRecords, "deletedAt")) and numberOfSelectedRecords < 10000',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.DESTROY_RECORDS,