Decouple Send Email node from workflows (#13322)

- Renamed `WorkflowActionAdapter` to `ToolExecutorWorkflowAction`
- Renamed `settingPermission` table to `permissionFlag` and `setting`
column to `flag`
- Decoupled the send email logic from workflows to tools
- Add new `Tools Permission` section in FE

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Abdul Rahman
2025-07-24 16:01:33 +05:30
committed by GitHub
parent eb404478c3
commit e93adde4b8
98 changed files with 1076 additions and 705 deletions

View File

@ -7,7 +7,7 @@ import { createOneObjectMetadataQueryFactory } from 'test/integration/metadata/s
import { deleteOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
@ -70,13 +70,13 @@ describe('Granular settings permissions', () => {
// Assign specific setting permissions to the custom role
const upsertSettingPermissionsQuery = {
query: `
mutation UpsertSettingPermissions {
upsertSettingPermissions(upsertSettingPermissionsInput: {
mutation UpsertPermissionFlags {
upsertPermissionFlags(upsertPermissionFlagsInput: {
roleId: "${customRoleId}"
settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}, ${SettingPermissionType.WORKFLOWS}]
permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}, ${PermissionFlagType.WORKFLOWS}]
}) {
id
setting
flag
roleId
}
}
@ -357,8 +357,8 @@ describe('Granular settings permissions', () => {
id
label
canUpdateAllSettings
settingPermissions {
setting
permissionFlags {
flag
}
}
}
@ -376,13 +376,13 @@ describe('Granular settings permissions', () => {
expect(customRole).toBeDefined();
expect(customRole.canUpdateAllSettings).toBe(false);
expect(customRole.settingPermissions).toHaveLength(3);
expect(
customRole.settingPermissions.map((p: any) => p.setting),
).toContain(SettingPermissionType.DATA_MODEL);
expect(
customRole.settingPermissions.map((p: any) => p.setting),
).toContain(SettingPermissionType.WORKSPACE);
expect(customRole.permissionFlags).toHaveLength(3);
expect(customRole.permissionFlags.map((p: any) => p.flag)).toContain(
PermissionFlagType.DATA_MODEL,
);
expect(customRole.permissionFlags.map((p: any) => p.flag)).toContain(
PermissionFlagType.WORKSPACE,
);
});
});
@ -391,13 +391,13 @@ describe('Granular settings permissions', () => {
// Add SECURITY permission to the custom role
const upsertSecurityPermissionQuery = {
query: `
mutation UpsertSettingPermissions {
upsertSettingPermissions(upsertSettingPermissionsInput: {
mutation UpsertPermissionFlags {
upsertPermissionFlags(upsertPermissionFlagsInput: {
roleId: "${customRoleId}"
settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}, ${SettingPermissionType.SECURITY}]
permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}, ${PermissionFlagType.SECURITY}]
}) {
id
setting
flag
roleId
}
}
@ -411,7 +411,7 @@ describe('Granular settings permissions', () => {
expect(response.status).toBe(200);
expect(response.body.errors).toBeUndefined();
expect(response.body.data.upsertSettingPermissions).toHaveLength(3);
expect(response.body.data.upsertPermissionFlags).toHaveLength(3);
// Verify the user now has access to security operations
// Note: This would require a specific security operation to test
@ -421,8 +421,8 @@ describe('Granular settings permissions', () => {
query GetRole {
getRoles {
id
settingPermissions {
setting
permissionFlags {
flag
}
}
}
@ -438,23 +438,23 @@ describe('Granular settings permissions', () => {
(role: any) => role.id === customRoleId,
);
expect(updatedRole.settingPermissions).toHaveLength(3);
expect(
updatedRole.settingPermissions.map((p: any) => p.setting),
).toContain(SettingPermissionType.SECURITY);
expect(updatedRole.permissionFlags).toHaveLength(3);
expect(updatedRole.permissionFlags.map((p: any) => p.flag)).toContain(
PermissionFlagType.SECURITY,
);
});
it('should allow removing setting permissions from existing role', async () => {
// Remove SECURITY permission, keep only DATA_MODEL and WORKSPACE
const upsertReducedPermissionsQuery = {
query: `
mutation UpsertSettingPermissions {
upsertSettingPermissions(upsertSettingPermissionsInput: {
mutation UpsertPermissionFlags {
upsertPermissionFlags(upsertPermissionFlagsInput: {
roleId: "${customRoleId}"
settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}]
permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}]
}) {
id
setting
flag
roleId
}
}
@ -468,7 +468,7 @@ describe('Granular settings permissions', () => {
expect(response.status).toBe(200);
expect(response.body.errors).toBeUndefined();
expect(response.body.data.upsertSettingPermissions).toHaveLength(2);
expect(response.body.data.upsertPermissionFlags).toHaveLength(2);
// Verify SECURITY permission was removed
const getRoleQuery = {
@ -476,8 +476,8 @@ describe('Granular settings permissions', () => {
query GetRole {
getRoles {
id
settingPermissions {
setting
permissionFlags {
flag
}
}
}
@ -493,10 +493,10 @@ describe('Granular settings permissions', () => {
(role: any) => role.id === customRoleId,
);
expect(updatedRole.settingPermissions).toHaveLength(2);
expect(
updatedRole.settingPermissions.map((p: any) => p.setting),
).not.toContain(SettingPermissionType.SECURITY);
expect(updatedRole.permissionFlags).toHaveLength(2);
expect(updatedRole.permissionFlags.map((p: any) => p.flag)).not.toContain(
PermissionFlagType.SECURITY,
);
});
});
});

View File

@ -5,7 +5,7 @@ import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object
import { fieldTextMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
@ -561,17 +561,17 @@ describe('roles permissions', () => {
});
});
describe('upsertSettingPermissions', () => {
describe('upsertPermissionFlags', () => {
const upsertSettingPermissionsMutation = ({
roleId,
}: {
roleId: string;
}) => `
mutation UpsertSettingPermissions {
upsertSettingPermissions(upsertSettingPermissionsInput: {roleId: "${roleId}", settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}]}) {
mutation UpsertPermissionFlags {
upsertPermissionFlags(upsertPermissionFlagsInput: {roleId: "${roleId}", permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}]}) {
id
roleId
setting
flag
}
}
`;
@ -625,11 +625,11 @@ describe('roles permissions', () => {
.expect((res) => {
expect(res.body.data).toBeDefined();
expect(res.body.errors).toBeUndefined();
expect(res.body.data.upsertSettingPermissions).toEqual(
expect(res.body.data.upsertPermissionFlags).toEqual(
expect.arrayContaining([
expect.objectContaining({
roleId: createdEditableRoleId,
setting: SettingPermissionType.DATA_MODEL,
flag: PermissionFlagType.DATA_MODEL,
}),
]),
);