[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:
+39
-35
@@ -11,6 +11,29 @@ import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMeta
|
||||
jest.mock('@/object-metadata/hooks/useObjectMetadataItem');
|
||||
jest.mock('@/object-record/utils/generateAggregateQuery');
|
||||
|
||||
const fields = [
|
||||
{
|
||||
id: '20202020-fed9-4ce5-9502-02a8efaf46e1',
|
||||
name: 'amount',
|
||||
label: 'Amount',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as FieldMetadataItem,
|
||||
{
|
||||
id: '20202020-dd4a-4ea4-bb7b-1c7300491b65',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as FieldMetadataItem,
|
||||
];
|
||||
|
||||
const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
@@ -23,28 +46,9 @@ const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
fields: [
|
||||
{
|
||||
id: '20202020-fed9-4ce5-9502-02a8efaf46e1',
|
||||
name: 'amount',
|
||||
label: 'Amount',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as FieldMetadataItem,
|
||||
{
|
||||
id: '20202020-dd4a-4ea4-bb7b-1c7300491b65',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as FieldMetadataItem,
|
||||
],
|
||||
fields,
|
||||
readableFields: fields,
|
||||
updatableFields: fields,
|
||||
indexMetadatas: [],
|
||||
isLabelSyncedWithName: true,
|
||||
isRemote: false,
|
||||
@@ -118,19 +122,19 @@ describe('useAggregateRecordsQuery', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid aggregation operation', () => {
|
||||
expect(() =>
|
||||
renderHook(
|
||||
() =>
|
||||
useAggregateRecordsQuery({
|
||||
objectNameSingular: 'company',
|
||||
recordGqlFieldsAggregate: {
|
||||
name: [AggregateOperations.SUM],
|
||||
},
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
),
|
||||
).toThrow();
|
||||
it('should early return for invalid aggregation operation', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useAggregateRecordsQuery({
|
||||
objectNameSingular: 'company',
|
||||
recordGqlFieldsAggregate: {
|
||||
name: [AggregateOperations.SUM],
|
||||
},
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
);
|
||||
|
||||
expect(result.current.gqlFieldToFieldMap).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle multiple aggregations', () => {
|
||||
|
||||
@@ -28,8 +28,11 @@ export const useAggregateRecordsQuery = ({
|
||||
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
const availableAggregations = useMemo(
|
||||
() => getAvailableAggregationsFromObjectFields(objectMetadataItem.fields),
|
||||
[objectMetadataItem.fields],
|
||||
() =>
|
||||
getAvailableAggregationsFromObjectFields(
|
||||
objectMetadataItem.readableFields,
|
||||
),
|
||||
[objectMetadataItem.readableFields],
|
||||
);
|
||||
|
||||
const recordGqlFields: RecordGqlFields = {};
|
||||
@@ -42,9 +45,7 @@ export const useAggregateRecordsQuery = ({
|
||||
availableAggregations[fieldName]?.[aggregateOperation];
|
||||
|
||||
if (!isDefined(fieldToQuery)) {
|
||||
throw new Error(
|
||||
`Cannot query operation ${aggregateOperation} on field ${fieldName}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
gqlFieldToFieldMap[fieldToQuery] = [fieldName, aggregateOperation];
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ export const useAttachRelatedRecordFromRecord = ({
|
||||
objectNameSingular: recordObjectNameSingular,
|
||||
});
|
||||
|
||||
const fieldOnObject = objectMetadataItem.fields.find((field) => {
|
||||
const fieldOnObject = objectMetadataItem.readableFields.find((field) => {
|
||||
return field.name === fieldNameOnRecordObject;
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ export const useDetachRelatedRecordFromRecord = ({
|
||||
objectNameSingular: recordObjectNameSingular,
|
||||
});
|
||||
|
||||
const fieldOnObject = objectMetadataItem.fields.find((field) => {
|
||||
const fieldOnObject = objectMetadataItem.readableFields.find((field) => {
|
||||
return field.name === fieldNameOnRecordObject;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated/graphql';
|
||||
|
||||
type useObjectPermissionsReturnType = {
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
objectPermissionsByObjectMetadataId: Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>;
|
||||
};
|
||||
|
||||
export const useObjectPermissions = (): useObjectPermissionsReturnType => {
|
||||
@@ -19,7 +22,10 @@ export const useObjectPermissions = (): useObjectPermissionsReturnType => {
|
||||
}
|
||||
|
||||
const objectPermissionsByObjectMetadataId = objectPermissions?.reduce(
|
||||
(acc: Record<string, ObjectPermission>, objectPermission) => {
|
||||
(
|
||||
acc: Record<string, ObjectPermissions & { objectMetadataId: string }>,
|
||||
objectPermission,
|
||||
) => {
|
||||
acc[objectPermission.objectMetadataId] = objectPermission;
|
||||
return acc;
|
||||
},
|
||||
|
||||
+4
-1
@@ -1,9 +1,12 @@
|
||||
import { getObjectPermissionsForObject } from '~/modules/object-metadata/utils/getObjectPermissionsForObject';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { ObjectPermissions } from 'twenty-shared/types';
|
||||
import { useObjectPermissions } from './useObjectPermissions';
|
||||
|
||||
export const useObjectPermissionsForObject = (objectMetadataId: string) => {
|
||||
export const useObjectPermissionsForObject = (
|
||||
objectMetadataId: string,
|
||||
): ObjectPermissions & { objectMetadataId: string } => {
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
return useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user