Fix readonly mode with permissions v2 for tables (#12617)
isReadonly was not set anymore, this PR put it back with the new permission check Also fix missing readonly mode for title cell
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import {
|
||||
FieldContext,
|
||||
@ -33,6 +34,10 @@ export const FieldContextProvider = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const fieldMetadataItem = objectMetadataItem?.fields.find(
|
||||
(field) => field.name === fieldMetadataName,
|
||||
);
|
||||
@ -56,6 +61,8 @@ export const FieldContextProvider = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const isObjectReadOnly = !objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
@ -73,7 +80,7 @@ export const FieldContextProvider = ({
|
||||
customUseUpdateOneObjectHook ?? useUpdateOneObjectMutation,
|
||||
clearable,
|
||||
overridenIsFieldEmpty,
|
||||
isReadOnly: false,
|
||||
isReadOnly: isObjectReadOnly,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -308,7 +308,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isReadOnly: false,
|
||||
isReadOnly: isFieldReadOnly,
|
||||
}}
|
||||
>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
|
||||
@ -5,8 +5,6 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
|
||||
import { RecordDetailRelationSectionDropdown } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdown';
|
||||
@ -117,17 +115,7 @@ export const RecordDetailRelationSection = ({
|
||||
},
|
||||
});
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId,
|
||||
objectMetadataId: relationObjectMetadataItem.id,
|
||||
});
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||
fieldDefinition,
|
||||
isRecordReadOnly,
|
||||
});
|
||||
|
||||
if (loading || isFieldReadOnly) return null;
|
||||
if (loading) return null;
|
||||
|
||||
const relationRecordsCount = relationAggregateResult?.id?.COUNT ?? 0;
|
||||
|
||||
|
||||
@ -44,10 +44,16 @@ export const RecordDetailRelationSectionDropdown = ({
|
||||
relationFieldMetadataId,
|
||||
relationObjectMetadataNameSingular,
|
||||
relationType,
|
||||
objectMetadataNameSingular,
|
||||
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||
|
||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const { objectMetadataItem: recordObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: objectMetadataNameSingular ?? '',
|
||||
});
|
||||
|
||||
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: relationObjectMetadataNameSingular,
|
||||
@ -148,7 +154,9 @@ export const RecordDetailRelationSectionDropdown = ({
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId,
|
||||
objectMetadataId: relationObjectMetadataItem.id,
|
||||
objectMetadataId: isToOneObject
|
||||
? recordObjectMetadataItem.id
|
||||
: relationObjectMetadataItem.id,
|
||||
});
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
@ -27,6 +28,10 @@ export const RecordTableCellPortalWrapper = ({
|
||||
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const visibleTableColumns = useRecoilComponentValueV2(
|
||||
visibleTableColumnsComponentSelector,
|
||||
);
|
||||
@ -36,6 +41,8 @@ export const RecordTableCellPortalWrapper = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const isReadOnly = !objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<RecordTableRowContextProvider
|
||||
value={{
|
||||
@ -48,6 +55,7 @@ export const RecordTableCellPortalWrapper = ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
}) + recordId,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isReadOnly,
|
||||
}}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
@ -84,6 +85,9 @@ export const RecordTableTr = forwardRef<
|
||||
RecordTableTrProps
|
||||
>(({ children, recordId, focusIndex, isDragging = false, ...props }, ref) => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
const currentRowSelected = useRecoilComponentFamilyValueV2(
|
||||
isRowSelectedComponentFamilyState,
|
||||
recordId,
|
||||
@ -121,6 +125,8 @@ export const RecordTableTr = forwardRef<
|
||||
const isNextRowActiveOrFocused =
|
||||
(isRowFocusActive && isNextRowFocused) || isNextRowActive;
|
||||
|
||||
const isReadOnly = !objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
return (
|
||||
<RecordTableRowContextProvider
|
||||
value={{
|
||||
@ -133,6 +139,7 @@ export const RecordTableTr = forwardRef<
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isSelected: currentRowSelected,
|
||||
inView: isRowVisible,
|
||||
isReadOnly,
|
||||
}}
|
||||
>
|
||||
<StyledTr
|
||||
|
||||
@ -32,7 +32,7 @@ export const RecordTitleCell = ({
|
||||
sizeVariant,
|
||||
containerType,
|
||||
}: RecordTitleCellProps) => {
|
||||
const { fieldDefinition, recordId } = useContext(FieldContext);
|
||||
const { fieldDefinition, recordId, isReadOnly } = useContext(FieldContext);
|
||||
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
|
||||
@ -82,6 +82,7 @@ export const RecordTitleCell = ({
|
||||
displayModeContent: <RecordTitleCellFieldDisplay />,
|
||||
editModeContentOnly: isFieldInputOnly,
|
||||
loading: loading,
|
||||
isReadOnly,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { RecordTitleCellContext } from '@/object-record/record-title-cell/components/RecordTitleCellContext';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RecordTitleCellContainer = () => {
|
||||
const { displayModeContent, editModeContent } = useContext(
|
||||
const { displayModeContent, editModeContent, isReadOnly } = useContext(
|
||||
RecordTitleCellContext,
|
||||
);
|
||||
|
||||
const { isInlineCellInEditMode } = useInlineCell();
|
||||
|
||||
if (isDefined(isReadOnly) && isReadOnly) {
|
||||
return <>{displayModeContent}</>;
|
||||
}
|
||||
|
||||
return <>{isInlineCellInEditMode ? editModeContent : displayModeContent}</>;
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ export type RecordTitleCellContextProps = {
|
||||
editModeContentOnly?: boolean;
|
||||
displayModeContent?: ReactElement;
|
||||
loading?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
};
|
||||
|
||||
const defaultRecordTitleCellContextProp: RecordTitleCellContextProps = {
|
||||
@ -12,6 +13,7 @@ const defaultRecordTitleCellContextProp: RecordTitleCellContextProps = {
|
||||
editModeContentOnly: false,
|
||||
displayModeContent: undefined,
|
||||
loading: false,
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
export const RecordTitleCellContext =
|
||||
|
||||
@ -24,7 +24,7 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { AvailableWorkspaces } from 'src/engine/core-modules/auth/dto/available-workspaces.output';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
@ -46,9 +46,12 @@ import {
|
||||
import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthProvider } from 'src/engine/decorators/auth/auth-provider.decorator';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions';
|
||||
@ -57,10 +60,6 @@ import { fromUserWorkspacePermissionsToUserWorkspacePermissionsDto } from 'src/e
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { AvailableWorkspaces } from 'src/engine/core-modules/auth/dto/available-workspaces.output';
|
||||
import { AuthProvider } from 'src/engine/decorators/auth/auth-provider.decorator';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
|
||||
const getHMACKey = (email?: string, key?: string | null) => {
|
||||
if (!email || !key) return null;
|
||||
@ -106,20 +105,7 @@ export class UserResolver {
|
||||
return this.permissionsService.getDefaultUserWorkspacePermissions();
|
||||
}
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.permissionsService.getUserWorkspacePermissionsV2({
|
||||
return await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
@ -27,7 +27,7 @@ export class PermissionsService {
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
public async getUserWorkspacePermissionsV2({
|
||||
public async getUserWorkspacePermissions({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
@ -117,68 +117,6 @@ export class PermissionsService {
|
||||
objectPermissions: {},
|
||||
}) as const satisfies UserWorkspacePermissions;
|
||||
|
||||
public async getUserWorkspacePermissions({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<UserWorkspacePermissions> {
|
||||
const [roleOfUserWorkspace] = await this.userRoleService
|
||||
.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspaceId],
|
||||
workspaceId,
|
||||
})
|
||||
.then((roles) => roles?.get(userWorkspaceId) ?? []);
|
||||
|
||||
let hasPermissionOnSettingFeature = false;
|
||||
|
||||
if (!isDefined(roleOfUserWorkspace)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
|
||||
PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
|
||||
);
|
||||
}
|
||||
|
||||
if (roleOfUserWorkspace.canUpdateAllSettings === true) {
|
||||
hasPermissionOnSettingFeature = true;
|
||||
}
|
||||
|
||||
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
|
||||
|
||||
const defaultSettingsPermissions =
|
||||
this.getDefaultUserWorkspacePermissions().settingsPermissions;
|
||||
const settingsPermissions = Object.keys(SettingPermissionType).reduce(
|
||||
(acc, feature) => ({
|
||||
...acc,
|
||||
[feature]:
|
||||
hasPermissionOnSettingFeature ||
|
||||
settingPermissions.some(
|
||||
(settingPermission) => settingPermission.setting === feature,
|
||||
),
|
||||
}),
|
||||
defaultSettingsPermissions,
|
||||
);
|
||||
|
||||
const objectRecordsPermissions: UserWorkspacePermissions['objectRecordsPermissions'] =
|
||||
{
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canReadAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canUpdateAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canDestroyAllObjectRecords ?? false,
|
||||
};
|
||||
|
||||
return {
|
||||
settingsPermissions,
|
||||
objectRecordsPermissions,
|
||||
objectPermissions: {},
|
||||
};
|
||||
}
|
||||
|
||||
public async userHasWorkspaceSettingPermission({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user