[permissions] QA fixes (#13698)
In this PR - Introduction of readableFields and updatableFields in objectMetadataItem selector to ease filtering from a developer experience perspective ( + to help developers think to do it). In discussion @lucasbordeau @charlesBochet - Remove non-updatable field from CSV import process (@etiennejouan) - QA fix / Non-readable fields should not show on show page - QA fix / It should not be offered to create a kanban view on a non-readable field - QA fix / It should not be offered to create view groups on a non-readable field - QA fix / Rating field should have a readonly mode --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+2
@@ -37,6 +37,7 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
objectMetadataId: personObjectMetadataItem.id,
|
||||
restrictedFields: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -140,6 +141,7 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
objectMetadataId: personObjectMetadataItem.id,
|
||||
restrictedFields: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { getNonReadableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonReadableFieldMetadataIdsFromObjectPermissions';
|
||||
import { getNonUpdatableFieldMetadataIdsFromObjectPermissions } from '@/object-metadata/utils/getNonUpdatableFieldMetadataIdsFromObjectPermissions';
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
type enrichObjectMetadataItemsWithPermissionsArgs = {
|
||||
objectMetadataItems: Omit<
|
||||
ObjectMetadataItem,
|
||||
'readableFields' | 'updatableFields'
|
||||
>[];
|
||||
objectPermissionsByObjectMetadataId: Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>;
|
||||
};
|
||||
|
||||
export const enrichObjectMetadataItemsWithPermissions = ({
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: enrichObjectMetadataItemsWithPermissionsArgs) => {
|
||||
const formattedObjects: ObjectMetadataItem[] =
|
||||
objectMetadataItems.map((object) => {
|
||||
const objectPermissions = getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: object.id,
|
||||
});
|
||||
|
||||
const nonReadableFieldMetadataIds = !isDefined(objectPermissions)
|
||||
? []
|
||||
: getNonReadableFieldMetadataIdsFromObjectPermissions({
|
||||
objectPermissions: objectPermissions,
|
||||
});
|
||||
|
||||
const nonUpdatableFieldMetadataIds = !isDefined(objectPermissions)
|
||||
? []
|
||||
: getNonUpdatableFieldMetadataIdsFromObjectPermissions({
|
||||
objectPermissions: objectPermissions,
|
||||
});
|
||||
|
||||
const { fields, ...objectWithoutFields } = object;
|
||||
|
||||
return {
|
||||
...objectWithoutFields,
|
||||
fields: fields,
|
||||
readableFields: fields.filter(
|
||||
(field) => !nonReadableFieldMetadataIds.includes(field.id),
|
||||
),
|
||||
updatableFields: fields.filter(
|
||||
(field) => !nonUpdatableFieldMetadataIds.includes(field.id),
|
||||
),
|
||||
} satisfies ObjectMetadataItem;
|
||||
}) ?? [];
|
||||
|
||||
return formattedObjects;
|
||||
};
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const getActiveFieldMetadataItems = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'readableFields'>,
|
||||
) =>
|
||||
objectMetadataItem.fields.filter(
|
||||
objectMetadataItem.readableFields.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.isActive && !fieldMetadataItem.isSystem,
|
||||
);
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
|
||||
type GetNonReadableFieldMetadataIdsFromObjectPermissionsArgs = {
|
||||
objectPermissions: ObjectPermissions;
|
||||
};
|
||||
|
||||
export const getNonReadableFieldMetadataIdsFromObjectPermissions = ({
|
||||
objectPermissions,
|
||||
}: GetNonReadableFieldMetadataIdsFromObjectPermissionsArgs) => {
|
||||
const restrictedFields = objectPermissions.restrictedFields;
|
||||
|
||||
return Object.entries(restrictedFields)
|
||||
.filter(([_, restrictedField]) => restrictedField.canRead === false)
|
||||
.map(([fieldMetadataId]) => fieldMetadataId);
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
|
||||
type GetNonUpdatableFieldMetadataIdsFromObjectPermissionsArgs = {
|
||||
objectPermissions: ObjectPermissions;
|
||||
};
|
||||
|
||||
export const getNonUpdatableFieldMetadataIdsFromObjectPermissions = ({
|
||||
objectPermissions,
|
||||
}: GetNonUpdatableFieldMetadataIdsFromObjectPermissionsArgs): string[] => {
|
||||
const restrictedFields = objectPermissions.restrictedFields;
|
||||
|
||||
return Object.entries(restrictedFields)
|
||||
.filter(([_, restrictedField]) => restrictedField.canUpdate === false)
|
||||
.map(([fieldMetadataId]) => fieldMetadataId);
|
||||
};
|
||||
+11
-5
@@ -1,12 +1,18 @@
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated/graphql';
|
||||
|
||||
export const getObjectPermissionsForObject = (
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>,
|
||||
objectPermissionsByObjectMetadataId: Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>,
|
||||
objectMetadataId: string,
|
||||
): ObjectPermission => {
|
||||
const objectPermissions =
|
||||
objectPermissionsByObjectMetadataId[objectMetadataId];
|
||||
): ObjectPermissions & { objectMetadataId: string } => {
|
||||
const objectPermissions = getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId,
|
||||
});
|
||||
|
||||
if (!isDefined(objectPermissions)) {
|
||||
return {
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { RestrictedField } from 'twenty-shared/types';
|
||||
import { ObjectPermission } from '~/generated/graphql';
|
||||
|
||||
export const getReadRestrictedFieldMetadataIdsFromObjectPermissions = ({
|
||||
objectPermissions,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
objectPermissions?: ObjectPermission[] | null;
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
const restrictedFields = objectPermissions?.find(
|
||||
(permission) => permission.objectMetadataId === objectMetadataId,
|
||||
)?.restrictedFields;
|
||||
|
||||
return Object.entries(restrictedFields ?? {})
|
||||
.filter(
|
||||
([_fieldMetadataId, restrictedField]) =>
|
||||
(restrictedField as RestrictedField).canRead === false,
|
||||
)
|
||||
.map(([fieldMetadataId]) => fieldMetadataId);
|
||||
};
|
||||
+6
-6
@@ -1,10 +1,6 @@
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectPermission,
|
||||
RelationType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
@@ -12,6 +8,7 @@ import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||
import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
|
||||
type MapFieldMetadataToGraphQLQueryArgs = {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
@@ -22,7 +19,10 @@ type MapFieldMetadataToGraphQLQueryArgs = {
|
||||
>;
|
||||
relationRecordGqlFields?: RecordGqlFields;
|
||||
computeReferences?: boolean;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
objectPermissionsByObjectMetadataId: Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>;
|
||||
isFieldsPermissionsEnabled?: boolean;
|
||||
};
|
||||
// TODO: change ObjectMetadataItems mock before refactoring with relation computed field
|
||||
|
||||
+9
-21
@@ -1,24 +1,25 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
||||
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
|
||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, ObjectPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated/graphql';
|
||||
|
||||
type MapObjectMetadataToGraphQLQueryArgs = {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
objectMetadataItem: Pick<
|
||||
ObjectMetadataItem,
|
||||
'nameSingular' | 'fields' | 'id'
|
||||
'nameSingular' | 'fields' | 'id' | 'readableFields'
|
||||
>;
|
||||
recordGqlFields?: RecordGqlFields;
|
||||
computeReferences?: boolean;
|
||||
isRootLevel?: boolean;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
objectPermissionsByObjectMetadataId: Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>;
|
||||
isFieldsPermissionsEnabled?: boolean;
|
||||
};
|
||||
|
||||
@@ -45,22 +46,10 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
}
|
||||
}
|
||||
|
||||
const filterReadableFields = (field: FieldMetadataItem) => {
|
||||
if (isFieldsPermissionsEnabled !== true) {
|
||||
return true;
|
||||
}
|
||||
const objectPermission = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
return objectPermission.restrictedFields?.[field.id]?.canRead !== false;
|
||||
};
|
||||
|
||||
const manyToOneRelationFields = objectMetadataItem?.fields
|
||||
const manyToOneRelationFields = objectMetadataItem?.readableFields
|
||||
.filter((field) => field.isActive)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => isDefined(field.settings?.joinColumnName))
|
||||
.filter(filterReadableFields);
|
||||
.filter((field) => isDefined(field.settings?.joinColumnName));
|
||||
|
||||
const manyToOneRelationGqlFieldWithFieldMetadata =
|
||||
manyToOneRelationFields.map((field) => ({
|
||||
@@ -69,9 +58,8 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
}));
|
||||
|
||||
const gqlFieldWithFieldMetadataThatCouldBeQueried = [
|
||||
...objectMetadataItem.fields
|
||||
...objectMetadataItem.readableFields
|
||||
.filter((fieldMetadata) => fieldMetadata.isActive)
|
||||
.filter(filterReadableFields)
|
||||
.map((fieldMetadata) => ({
|
||||
gqlField: fieldMetadata.name,
|
||||
fieldMetadata,
|
||||
|
||||
+13
-5
@@ -4,12 +4,17 @@ import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/o
|
||||
import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
|
||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||
|
||||
type mapPaginatedObjectMetadataItemsToObjectMetadataItemsArgs = {
|
||||
pagedObjectMetadataItems: ObjectMetadataItemsQuery | undefined;
|
||||
};
|
||||
|
||||
export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({
|
||||
pagedObjectMetadataItems,
|
||||
}: {
|
||||
pagedObjectMetadataItems: ObjectMetadataItemsQuery | undefined;
|
||||
}) => {
|
||||
const formattedObjects: ObjectMetadataItem[] =
|
||||
}: mapPaginatedObjectMetadataItemsToObjectMetadataItemsArgs) => {
|
||||
const formattedObjects: Omit<
|
||||
ObjectMetadataItem,
|
||||
'readableFields' | 'updatableFields'
|
||||
>[] =
|
||||
pagedObjectMetadataItems?.objects.edges.map((object) => {
|
||||
const labelIdentifierFieldMetadataId =
|
||||
objectMetadataItemSchema.shape.labelIdentifierFieldMetadataId.parse(
|
||||
@@ -35,7 +40,10 @@ export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({
|
||||
),
|
||||
}) satisfies IndexMetadataItem,
|
||||
),
|
||||
} satisfies ObjectMetadataItem;
|
||||
} satisfies Omit<
|
||||
ObjectMetadataItem,
|
||||
'readableFields' | 'updatableFields'
|
||||
>;
|
||||
}) ?? [];
|
||||
|
||||
return formattedObjects;
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const mapSoftDeleteFieldsToGraphQLQuery = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'readableFields'>,
|
||||
): string => {
|
||||
const softDeleteFields = ['deletedAt', 'id'];
|
||||
|
||||
const fieldsThatShouldBeQueried = objectMetadataItem.fields
|
||||
const fieldsThatShouldBeQueried = objectMetadataItem.readableFields
|
||||
.filter((field) => field.isActive && softDeleteFields.includes(field.name))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
|
||||
Reference in New Issue
Block a user