[FE] handle restricted objects 2 (#12437)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/components/NavigationDrawerItemForObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
@ -27,6 +29,8 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
||||
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
|
||||
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const sortedStandardObjectMetadataItems = [...objectMetadataItems]
|
||||
.filter((item) => ORDERED_STANDARD_OBJECTS.includes(item.nameSingular))
|
||||
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
||||
@ -58,6 +62,15 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
||||
...sortedCustomObjectMetadataItems,
|
||||
];
|
||||
|
||||
const objectMetadataItemsForNavigationItemsWithReadPermission =
|
||||
objectMetadataItemsForNavigationItems.filter(
|
||||
(objectMetadataItem) =>
|
||||
getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
).canReadObjectRecords,
|
||||
);
|
||||
|
||||
return (
|
||||
objectMetadataItems.length > 0 && (
|
||||
<NavigationDrawerSection>
|
||||
@ -68,12 +81,14 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
||||
/>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
{isNavigationSectionOpen &&
|
||||
objectMetadataItemsForNavigationItems.map((objectMetadataItem) => (
|
||||
<NavigationDrawerItemForObjectMetadataItem
|
||||
key={`navigation-drawer-item-${objectMetadataItem.id}`}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
))}
|
||||
objectMetadataItemsForNavigationItemsWithReadPermission.map(
|
||||
(objectMetadataItem) => (
|
||||
<NavigationDrawerItemForObjectMetadataItem
|
||||
key={`navigation-drawer-item-${objectMetadataItem.id}`}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</NavigationDrawerSection>
|
||||
)
|
||||
);
|
||||
|
||||
@ -18,6 +18,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'id',
|
||||
)!,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
expect(normalizeGQLField(res)).toEqual(normalizeGQLField('id'));
|
||||
});
|
||||
@ -28,6 +29,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'name',
|
||||
)!,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
expect(normalizeGQLField(res)).toEqual(
|
||||
normalizeGQLField(`name
|
||||
@ -45,6 +47,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'company',
|
||||
)!,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
expect(normalizeGQLField(res)).toEqual(
|
||||
normalizeGQLField(`company
|
||||
@ -122,6 +125,7 @@ idealCustomerProfile
|
||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'company',
|
||||
)!,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
expect(normalizeGQLField(res)).toEqual(
|
||||
normalizeGQLField(`company
|
||||
|
||||
@ -30,6 +30,15 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
|
||||
avatarUrl: true,
|
||||
companyId: true,
|
||||
},
|
||||
objectPermissionsByObjectMetadataId: {
|
||||
[personObjectMetadataItem.id]: {
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
objectMetadataId: personObjectMetadataItem.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(normalizeGQLQuery(res)).toEqual(
|
||||
normalizeGQLQuery(`{
|
||||
@ -124,6 +133,15 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
|
||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||
objectMetadataItem: personObjectMetadataItem,
|
||||
recordGqlFields: { company: { id: true }, id: true, name: true },
|
||||
objectPermissionsByObjectMetadataId: {
|
||||
[personObjectMetadataItem.id]: {
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
objectMetadataId: personObjectMetadataItem.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(normalizeGQLQuery(res)).toEqual(
|
||||
normalizeGQLQuery(`{
|
||||
|
||||
@ -33,6 +33,7 @@ export const formatFieldMetadataItemAsFieldDefinition = ({
|
||||
relationObjectMetadataItem?.nameSingular ?? '',
|
||||
relationObjectMetadataNamePlural:
|
||||
relationObjectMetadataItem?.namePlural ?? '',
|
||||
relationObjectMetadataId: relationObjectMetadataItem?.id ?? '',
|
||||
objectMetadataNameSingular: objectMetadataItem.nameSingular ?? '',
|
||||
targetFieldMetadataName:
|
||||
field.relationDefinition?.targetFieldMetadata?.name ?? '',
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
import { ObjectPermissions } from '@/object-record/cache/types/ObjectPermissions';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getObjectPermissionsForObject = (
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>,
|
||||
objectMetadataId: string,
|
||||
): ObjectPermissions => {
|
||||
const objectPermissions =
|
||||
objectPermissionsByObjectMetadataId[objectMetadataId];
|
||||
|
||||
if (!isDefined(objectPermissions)) {
|
||||
return {
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
canReadObjectRecords: objectPermissions.canReadObjectRecords ?? true,
|
||||
canUpdateObjectRecords: objectPermissions.canUpdateObjectRecords ?? true,
|
||||
canSoftDeleteObjectRecords:
|
||||
objectPermissions.canSoftDeleteObjectRecords ?? true,
|
||||
canDestroyObjectRecords: objectPermissions.canDestroyObjectRecords ?? true,
|
||||
};
|
||||
};
|
||||
@ -2,12 +2,15 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ObjectPermission,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
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';
|
||||
|
||||
type MapFieldMetadataToGraphQLQueryArgs = {
|
||||
@ -19,6 +22,7 @@ type MapFieldMetadataToGraphQLQueryArgs = {
|
||||
>;
|
||||
relationRecordGqlFields?: RecordGqlFields;
|
||||
computeReferences?: boolean;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
};
|
||||
// TODO: change ObjectMetadataItems mock before refactoring with relationDefinition computed field
|
||||
export const mapFieldMetadataToGraphQLQuery = ({
|
||||
@ -27,11 +31,17 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
fieldMetadata,
|
||||
relationRecordGqlFields,
|
||||
computeReferences = false,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: MapFieldMetadataToGraphQLQueryArgs): string => {
|
||||
const fieldType = fieldMetadata.type;
|
||||
|
||||
const fieldIsNonCompositeField = isNonCompositeField(fieldType);
|
||||
|
||||
const objectPermission = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
fieldMetadata.relationDefinition?.targetObjectMetadata.id,
|
||||
);
|
||||
|
||||
if (fieldIsNonCompositeField) {
|
||||
return gqlField;
|
||||
}
|
||||
@ -51,6 +61,15 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
||||
return '';
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(objectPermissionsByObjectMetadataId) &&
|
||||
isDefined(relationMetadataItem.id)
|
||||
) {
|
||||
if (!objectPermission.canReadObjectRecords) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (gqlField === fieldMetadata.settings?.joinColumnName) {
|
||||
return `${gqlField}`;
|
||||
}
|
||||
@ -62,6 +81,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
recordGqlFields: relationRecordGqlFields,
|
||||
computeReferences: computeReferences,
|
||||
isRootLevel: false,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}`;
|
||||
}
|
||||
|
||||
@ -80,6 +100,15 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
return '';
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(objectPermissionsByObjectMetadataId) &&
|
||||
isDefined(relationMetadataItem.id)
|
||||
) {
|
||||
if (!objectPermission.canReadObjectRecords) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return `${gqlField}
|
||||
{
|
||||
edges {
|
||||
@ -89,6 +118,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
||||
recordGqlFields: relationRecordGqlFields,
|
||||
computeReferences,
|
||||
isRootLevel: false,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}
|
||||
}
|
||||
}`;
|
||||
|
||||
@ -1,25 +1,47 @@
|
||||
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 { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
|
||||
type MapObjectMetadataToGraphQLQueryArgs = {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'nameSingular' | 'fields'>;
|
||||
objectMetadataItem: Pick<
|
||||
ObjectMetadataItem,
|
||||
'nameSingular' | 'fields' | 'id'
|
||||
>;
|
||||
recordGqlFields?: RecordGqlFields;
|
||||
computeReferences?: boolean;
|
||||
isRootLevel?: boolean;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
};
|
||||
|
||||
export const mapObjectMetadataToGraphQLQuery = ({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
recordGqlFields,
|
||||
computeReferences = false,
|
||||
isRootLevel = true,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: MapObjectMetadataToGraphQLQueryArgs): string => {
|
||||
if (
|
||||
!isRootLevel &&
|
||||
isDefined(objectPermissionsByObjectMetadataId) &&
|
||||
isDefined(objectMetadataItem.id)
|
||||
) {
|
||||
const objectPermission = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
if (!objectPermission.canReadObjectRecords) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const manyToOneRelationFields = objectMetadataItem?.fields
|
||||
.filter((field) => field.isActive)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
@ -61,25 +83,29 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
||||
}`;
|
||||
}
|
||||
|
||||
const mappedFields = gqlFieldWithFieldMetadataThatSouldBeQueried
|
||||
.map((gqlFieldWithFieldMetadata) => {
|
||||
const currentRecordGqlFields =
|
||||
recordGqlFields?.[gqlFieldWithFieldMetadata.gqlField];
|
||||
const relationRecordGqlFields = isRecordGqlFieldsNode(
|
||||
currentRecordGqlFields,
|
||||
)
|
||||
? currentRecordGqlFields
|
||||
: undefined;
|
||||
return mapFieldMetadataToGraphQLQuery({
|
||||
objectMetadataItems,
|
||||
gqlField: gqlFieldWithFieldMetadata.gqlField,
|
||||
fieldMetadata: gqlFieldWithFieldMetadata.fieldMetadata,
|
||||
relationRecordGqlFields,
|
||||
computeReferences,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
})
|
||||
.filter((field) => field !== '')
|
||||
.join('\n');
|
||||
|
||||
return `{
|
||||
__typename
|
||||
${gqlFieldWithFieldMetadataThatSouldBeQueried
|
||||
.map((gqlFieldWithFieldMetadata) => {
|
||||
const currentRecordGqlFields =
|
||||
recordGqlFields?.[gqlFieldWithFieldMetadata.gqlField];
|
||||
const relationRecordGqlFields = isRecordGqlFieldsNode(
|
||||
currentRecordGqlFields,
|
||||
)
|
||||
? currentRecordGqlFields
|
||||
: undefined;
|
||||
return mapFieldMetadataToGraphQLQuery({
|
||||
objectMetadataItems,
|
||||
gqlField: gqlFieldWithFieldMetadata.gqlField,
|
||||
fieldMetadata: gqlFieldWithFieldMetadata.fieldMetadata,
|
||||
relationRecordGqlFields,
|
||||
computeReferences,
|
||||
});
|
||||
})
|
||||
.join('\n')}
|
||||
${mappedFields}
|
||||
}`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user