Fix delete/restore/destroy commands unavailable in select-all mode (#19311)
Fixes https://github.com/twentyhq/twenty/issues/19309 In exclusion mode (select all), selectedRecords is always [] since individual record IDs aren't tracked. The noneDefined()/everyDefined() checks return false on empty arrays by design, which hides the delete, restore, and destroy commands from the command menu. - Wrap selectedRecords array checks with (isSelectAll or ...) to bypass when in exclusion mode - Remove the `numberOfSelectedRecords < 10000` limit - Add `upgrade:1-21:fix-select-all-command-menu-items` command to backfill existing workspaces - Add tests
This commit is contained in:
+75
@@ -307,6 +307,81 @@ describe('evaluateConditionalAvailabilityExpression', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSelectAll bypasses selectedRecords array checks', () => {
|
||||
it('should return true for "isSelectAll or noneDefined(...)" when isSelectAll is true and selectedRecords is empty', () => {
|
||||
const context = buildContext({
|
||||
isSelectAll: true,
|
||||
selectedRecords: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
evaluateConditionalAvailabilityExpression(
|
||||
'isSelectAll or noneDefined(selectedRecords, "deletedAt")',
|
||||
context,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for "isSelectAll or everyDefined(...)" when isSelectAll is true and selectedRecords is empty', () => {
|
||||
const context = buildContext({
|
||||
isSelectAll: true,
|
||||
selectedRecords: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
evaluateConditionalAvailabilityExpression(
|
||||
'isSelectAll or everyDefined(selectedRecords, "deletedAt")',
|
||||
context,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should evaluate the full deleteRecords expression to true in select-all mode', () => {
|
||||
const expression =
|
||||
'numberOfSelectedRecords >= 1 and not hasAnySoftDeleteFilterOnView and objectPermissions.canSoftDeleteObjectRecords and (isSelectAll or noneDefined(selectedRecords, "deletedAt"))';
|
||||
|
||||
const context = buildContext({
|
||||
isSelectAll: true,
|
||||
selectedRecords: [],
|
||||
numberOfSelectedRecords: 50,
|
||||
hasAnySoftDeleteFilterOnView: false,
|
||||
objectPermissions: {
|
||||
objectMetadataId: '',
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: false,
|
||||
restrictedFields: {},
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
evaluateConditionalAvailabilityExpression(expression, context),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should still respect noneDefined when isSelectAll is false and records are present', () => {
|
||||
const expression =
|
||||
'isSelectAll or noneDefined(selectedRecords, "deletedAt")';
|
||||
|
||||
const contextWithDeletedRecord = buildContext({
|
||||
isSelectAll: false,
|
||||
selectedRecords: [
|
||||
{ id: 'id-1', createdAt: '', updatedAt: '', deletedAt: '2024-01-01' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(
|
||||
evaluateConditionalAvailabilityExpression(
|
||||
expression,
|
||||
contextWithDeletedRecord,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activateWorkflow expression regression', () => {
|
||||
const activateWorkflowExpression =
|
||||
'everyDefined(selectedRecords, "currentVersion.trigger") and everyDefined(selectedRecords, "currentVersion.steps") and every(selectedRecords, "currentVersion.steps.length") and (everyEquals(selectedRecords, "currentVersion.status", "DRAFT") or includesNone(selectedRecords, "statuses", "ACTIVE")) and noneDefined(selectedRecords, "deletedAt")';
|
||||
|
||||
Reference in New Issue
Block a user