Role page various fixes (#12324)

Various fixes from fast follows

- Sort roles by alphabetical order
- Change some tooltips
- During role creation, role should have all permissions enabled by
default
- Changed Permission icons design and refactored duplicating logic in a
dedicated component
- Changed "Revoked by" design
- Display role icon in default role picker
- Workspace member avatar was missing in role list and member picker
- Set "seeded" member role as editable for new workspaces
- Various css fixes
This commit is contained in:
Weiko
2025-05-27 17:58:55 +02:00
committed by GitHub
parent 8051646567
commit f210d274bf
16 changed files with 214 additions and 137 deletions

View File

@ -7,7 +7,7 @@ import { Select } from '@/ui/input/components/Select';
import { t } from '@lingui/core/macro';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { H2Title, IconUserPin } from 'twenty-ui/display';
import { H2Title, IconUserPin, useIcons } from 'twenty-ui/display';
import { Card, Section } from 'twenty-ui/layout';
import {
Role,
@ -51,10 +51,18 @@ export const SettingsRoleDefaultRole = ({
});
};
const { getIcon } = useIcons();
if (!currentWorkspace || !defaultRole) {
return null;
}
const options = roles.map((role) => ({
label: role.label,
value: role.id,
Icon: getIcon(role.icon),
}));
return (
<Section>
<H2Title
@ -71,10 +79,7 @@ export const SettingsRoleDefaultRole = ({
selectSizeVariant="small"
withSearchInput
dropdownId="default-role-select"
options={roles.map((role) => ({
label: role.label,
value: role.id,
}))}
options={options}
value={defaultRole?.id ?? ''}
onChange={(value) =>
updateDefaultRole(value as string, currentWorkspace)

View File

@ -9,11 +9,12 @@ import { SettingsPath } from '@/types/SettingsPath';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { H2Title, IconPlus } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { FeatureFlagKey } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { Button } from 'twenty-ui/input';
import { H2Title, IconPlus } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { sortByAscString } from '~/utils/array/sortByAscString';
const StyledCreateRoleSection = styled(Section)`
border-top: 1px solid ${({ theme }) => theme.border.color.light};
@ -40,6 +41,10 @@ export const SettingsRolesList = () => {
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
const sortedSettingsAllRoles = [...settingsAllRoles].sort((a, b) =>
sortByAscString(a.label, b.label),
);
return (
<Section>
<H2Title
@ -49,10 +54,10 @@ export const SettingsRolesList = () => {
<Table>
<SettingsRolesTableHeader />
<StyledTableRows>
{settingsAllRoles.length === 0 ? (
{sortedSettingsAllRoles.length === 0 ? (
<StyledNoRoles>{t`No roles found`}</StyledNoRoles>
) : (
settingsAllRoles.map((role) => (
sortedSettingsAllRoles.map((role) => (
<SettingsRolesTableRow key={role.id} role={role} />
))
)}

View File

@ -111,11 +111,8 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
<TableCell align={'left'}>
<StyledAssignedText>{role.workspaceMembers.length}</StyledAssignedText>
</TableCell>
<TableCell align={'right'}>
<IconChevronRight
size={theme.icon.size.md}
color={theme.font.color.tertiary}
/>
<TableCell align={'right'} color={theme.font.color.tertiary}>
<IconChevronRight size={theme.icon.size.md} />
</TableCell>
</StyledTableRow>
);

View File

@ -243,6 +243,7 @@ export const SettingsRoleAssignment = ({
<Dropdown
dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
dropdownOffset={{ x: 0, y: 4 }}
clickableComponent={
<>
<div id="assign-member">
@ -256,7 +257,7 @@ export const SettingsRoleAssignment = ({
</div>
<AppTooltip
anchorSelect="#assign-member"
content={t`No more members to assign`}
content={t`The workspace needs at least one Admin`}
delay={TooltipDelay.noDelay}
hidden={!allWorkspaceMembersHaveThisRole}
/>

View File

@ -1,8 +1,8 @@
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { WorkspaceMember } from '~/generated-metadata/graphql';
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { WorkspaceMember } from '~/generated-metadata/graphql';
const StyledIconWrapper = styled.div`
align-items: center;

View File

@ -1,6 +1,6 @@
import { t } from '@lingui/core/macro';
import { SearchRecord } from '~/generated-metadata/graphql';
import { MenuItem, MenuItemAvatar } from 'twenty-ui/navigation';
import { SearchRecord } from '~/generated-metadata/graphql';
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
loading: boolean;
@ -34,6 +34,7 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({
size: 'md',
placeholder: workspaceMember.label ?? '',
placeholderColorSeed: workspaceMember.recordId,
avatarUrl: workspaceMember.imageUrl,
}}
text={workspaceMember.label}
/>

View File

@ -1,34 +1,12 @@
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission } from '~/generated/graphql';
const StyledIconWrapper = styled.div<{ isForbidden?: boolean }>`
align-items: center;
background: ${({ theme, isForbidden }) =>
isForbidden ? theme.adaptiveColors.orange1 : theme.adaptiveColors.blue1};
border: 1px solid
${({ theme, isForbidden }) =>
isForbidden ? theme.adaptiveColors.orange3 : theme.adaptiveColors.blue3};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
height: ${({ theme }) => theme.spacing(4)};
justify-content: center;
width: ${({ theme }) => theme.spacing(4)};
`;
const StyledIcon = styled.div<{ isForbidden?: boolean }>`
align-items: center;
display: flex;
color: ${({ theme, isForbidden }) =>
isForbidden ? theme.color.orange : theme.color.blue};
justify-content: center;
`;
const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
@ -43,8 +21,6 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
objectPermission,
roleId,
}: SettingsRolePermissionsObjectLevelOverrideCellProps) => {
const theme = useTheme();
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
@ -52,44 +28,33 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
const permissionMappings =
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
type ObjectPermissionKey = keyof typeof permissionMappings;
const isOverridden = (permission: ObjectPermissionKey) => {
const isOverridden = (permission: SettingsRoleObjectPermissionKey) => {
const rolePermission = permissionMappings[permission];
return (
isDefined(objectPermission[permission]) &&
!!settingsDraftRole[rolePermission as keyof typeof settingsDraftRole] !==
!!objectPermission[permission]
!!settingsDraftRole[rolePermission] !== !!objectPermission[permission]
);
};
return (
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
{(Object.keys(permissionMappings) as ObjectPermissionKey[]).map(
(permission) => {
const { Icon, IconForbidden: IconOverride } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
const permissionValue = objectPermission[permission];
{(
Object.keys(permissionMappings) as SettingsRoleObjectPermissionKey[]
).map((permission) => {
const permissionValue = objectPermission[permission];
if (!isOverridden(permission)) {
return null;
}
if (!isOverridden(permission)) {
return null;
}
return (
<StyledIconWrapper
key={permission}
isForbidden={permissionValue === false}
>
<StyledIcon isForbidden={permissionValue === false}>
{permissionValue === false && (
<IconOverride size={theme.icon.size.sm} />
)}
{permissionValue === true && <Icon size={theme.icon.size.sm} />}
</StyledIcon>
</StyledIconWrapper>
);
},
)}
return (
<PermissionIcon
key={permission}
permission={permission}
state={permissionValue === false ? 'revoked' : 'granted'}
/>
);
})}
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
);
};

View File

@ -130,6 +130,7 @@ export const SettingsRolePermissionsObjectLevelSection = ({
disabled={!isEditable}
/>
}
dropdownOffset={{ x: 0, y: 4 }}
dropdownComponents={
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
excludedObjectMetadataIds={

View File

@ -1,11 +1,11 @@
import { OverridableCheckbox } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/OverridableCheckbox';
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
@ -13,25 +13,36 @@ import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission } from '~/generated-metadata/graphql';
import type { Role } from '~/generated/graphql';
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.span`
background: ${({ theme }) => theme.adaptiveColors.orange1};
border-radius: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.color.orange};
padding: ${({ theme }) => theme.spacing(1)};
const StyledTableRow = styled(TableRow)`
align-items: center;
display: flex;
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
@ -39,11 +50,6 @@ const StyledCheckboxCell = styled(TableCell)`
padding-right: ${({ theme }) => theme.spacing(4)};
`;
const StyledTableRow = styled(TableRow)`
align-items: center;
display: flex;
`;
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
@ -60,17 +66,12 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
settingsDraftRoleObjectPermissions,
roleId,
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps) => {
const theme = useTheme();
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
const label = permission.label;
const { Icon } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
const permissionMappings =
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
@ -120,13 +121,21 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
return (
<StyledTableRow>
<StyledPermissionCell>
<Icon size={theme.icon.size.sm} />
<StyledLabel>{label}</StyledLabel>
{isRevoked ? (
<StyledOverrideInfo>
{t`Revoked for this object`}
</StyledOverrideInfo>
) : null}
<StyledPermissionContent>
<PermissionIcon
permission={permission.key as SettingsRoleObjectPermissionKey}
state={isRevoked ? 'revoked' : 'granted'}
/>
<StyledPermissionLabel>{label}</StyledPermissionLabel>
</StyledPermissionContent>
<StyledOverrideInfo>
{isRevoked ? (
<>
{' · '}
{t`Revoked for this object`}
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell>
<OverridableCheckbox

View File

@ -0,0 +1,51 @@
import {
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG,
SettingsRoleObjectPermissionKey,
} from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
type PermissionIconProps = {
permission: SettingsRoleObjectPermissionKey;
state: 'granted' | 'revoked';
};
const StyledIconWrapper = styled.div<{ isRevoked?: boolean }>`
align-items: center;
background: ${({ theme, isRevoked }) =>
isRevoked ? theme.adaptiveColors.orange1 : theme.adaptiveColors.blue1};
border: 1px solid
${({ theme, isRevoked }) =>
isRevoked ? theme.adaptiveColors.orange3 : theme.adaptiveColors.blue3};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
height: ${({ theme }) => theme.spacing(4)};
justify-content: center;
width: ${({ theme }) => theme.spacing(4)};
`;
const StyledIcon = styled.div<{ isRevoked?: boolean }>`
align-items: center;
display: flex;
color: ${({ theme, isRevoked }) =>
isRevoked ? theme.color.orange : theme.color.blue};
justify-content: center;
`;
export const PermissionIcon = ({ permission, state }: PermissionIconProps) => {
const theme = useTheme();
const { Icon, IconForbidden } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
const isRevoked = state === 'revoked';
return (
<StyledIconWrapper isRevoked={isRevoked}>
<StyledIcon isRevoked={isRevoked}>
{isRevoked && <IconForbidden size={theme.icon.size.sm} />}
{!isRevoked && <Icon size={theme.icon.size.sm} />}
</StyledIcon>
</StyledIconWrapper>
);
};

View File

@ -1,32 +1,37 @@
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import pluralize from 'pluralize';
import { Checkbox, CheckboxAccent } from 'twenty-ui/input';
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.span`
background: ${({ theme }) => theme.adaptiveColors.orange1};
border-radius: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.color.orange};
padding: ${({ theme }) => theme.spacing(1)};
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
@ -48,25 +53,30 @@ export const SettingsRolePermissionsObjectsTableRow = ({
permission,
isEditable,
}: SettingsRolePermissionsObjectsTableRowProps) => {
const theme = useTheme();
const revokedBy = permission.revokedBy;
const isRevoked = revokedBy && revokedBy > 0;
const isRevoked =
revokedBy !== undefined && revokedBy !== null && revokedBy > 0;
const label = permission.label;
const pluralizedObject = pluralize('object', revokedBy);
const { Icon } = SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
return (
<StyledTableRow>
<StyledPermissionCell>
<Icon size={theme.icon.size.sm} />
<StyledLabel>{label}</StyledLabel>
{isRevoked ? (
<StyledOverrideInfo>
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
</StyledOverrideInfo>
) : null}
<StyledPermissionContent>
<PermissionIcon
permission={permission.key as SettingsRoleObjectPermissionKey}
state={isRevoked ? 'revoked' : 'granted'}
/>
<StyledPermissionLabel>{label}</StyledPermissionLabel>
</StyledPermissionContent>
<StyledOverrideInfo>
{isRevoked ? (
<>
{' · '}
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell>
<Checkbox

View File

@ -14,8 +14,14 @@ type SettingsRoleObjectPermissionIconConfig = {
IconForbidden: IconComponent;
};
export type SettingsRoleObjectPermissionKey =
| 'canReadObjectRecords'
| 'canUpdateObjectRecords'
| 'canSoftDeleteObjectRecords'
| 'canDestroyObjectRecords';
export const SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG: Record<
string,
SettingsRoleObjectPermissionKey,
SettingsRoleObjectPermissionIconConfig
> = {
canReadObjectRecords: {

View File

@ -34,11 +34,11 @@ export const SettingsRoleCreateEffect = ({
label: '',
description: '',
icon: 'IconUser',
canUpdateAllSettings: false,
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: true,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: true,
isEditable: true,
workspaceMembers: [],
};

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -26,6 +27,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
ObjectPermissionModule,
SettingPermissionModule,
WorkspacePermissionsCacheModule,
FileModule,
],
providers: [RoleService, RoleResolver],
exports: [RoleService],

View File

@ -8,8 +8,12 @@ import {
Resolver,
} from '@nestjs/graphql';
import { buildSignedPath } from 'twenty-shared/utils';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -47,6 +51,7 @@ export class RoleResolver {
private readonly featureFlagService: FeatureFlagService,
private readonly objectPermissionService: ObjectPermissionService,
private readonly settingPermissionService: SettingPermissionService,
private readonly fileService: FileService,
) {}
@Query(() => [RoleDTO])
@ -180,10 +185,29 @@ export class RoleResolver {
@Parent() role: RoleDTO,
@AuthWorkspace() workspace: Workspace,
): Promise<WorkspaceMemberWorkspaceEntity[]> {
return this.userRoleService.getWorkspaceMembersAssignedToRole(
role.id,
workspace.id,
const workspaceMembers =
await this.userRoleService.getWorkspaceMembersAssignedToRole(
role.id,
workspace.id,
);
await Promise.all(
workspaceMembers.map(async (workspaceMember) => {
if (workspaceMember && workspaceMember.avatarUrl) {
const avatarUrlToken = this.fileService.encodeFileToken({
filename: extractFilenameFromPath(workspaceMember.avatarUrl),
workspaceId: workspace.id,
});
workspaceMember.avatarUrl = buildSignedPath({
path: workspaceMember.avatarUrl,
token: avatarUrlToken,
});
}
}),
);
return workspaceMembers;
}
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {

View File

@ -218,7 +218,7 @@ export class RoleService {
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: true,
isEditable: false,
isEditable: true,
workspaceId,
});
}