[FE] handle restricted objects 2 (#12437)

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2025-06-05 15:49:22 +02:00
committed by GitHub
parent ad804ebecd
commit 3f30964523
109 changed files with 904 additions and 306 deletions

View File

@ -8,6 +8,7 @@ import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateReco
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useQuery } from '@apollo/client';
import { renderHook } from '@testing-library/react';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
// Mocks
jest.mock('@apollo/client');
@ -25,6 +26,10 @@ const mockGqlFieldToFieldMap = {
totalCount: ['name', AggregateOperations.COUNT],
};
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});
describe('useAggregateRecords', () => {
beforeEach(() => {
(useObjectMetadataItem as jest.Mock).mockReturnValue({
@ -44,14 +49,18 @@ describe('useAggregateRecords', () => {
});
it('should format data correctly', () => {
const { result } = renderHook(() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM, AggregateOperations.AVG],
name: [AggregateOperations.COUNT],
},
}),
const { result } = renderHook(
() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM, AggregateOperations.AVG],
name: [AggregateOperations.COUNT],
},
}),
{
wrapper: Wrapper,
},
);
expect(result.current.data).toEqual({
@ -74,13 +83,17 @@ describe('useAggregateRecords', () => {
error: undefined,
});
const { result } = renderHook(() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM],
},
}),
const { result } = renderHook(
() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM],
},
}),
{
wrapper: Wrapper,
},
);
expect(result.current.data).toEqual({});
@ -95,13 +108,17 @@ describe('useAggregateRecords', () => {
error: mockError,
});
const { result } = renderHook(() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM],
},
}),
const { result } = renderHook(
() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM],
},
}),
{
wrapper: Wrapper,
},
);
expect(result.current.data).toEqual({});
@ -109,14 +126,18 @@ describe('useAggregateRecords', () => {
});
it('should skip query when specified', () => {
renderHook(() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM],
},
skip: true,
}),
renderHook(
() =>
useAggregateRecords({
objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: {
amount: [AggregateOperations.SUM],
},
skip: true,
}),
{
wrapper: Wrapper,
},
);
expect(useQuery).toHaveBeenCalledWith(

View File

@ -57,6 +57,7 @@ describe('useDeleteManyRecords', () => {
objectMetadataItem,
objectMetadataItems,
recordId: expectedRecord.id,
objectPermissionsByObjectMetadataId: {},
});
expect(cachedRecord).not.toBeNull();
if (cachedRecord === null) throw new Error('Should never occur');
@ -72,6 +73,7 @@ describe('useDeleteManyRecords', () => {
objectMetadataItem,
objectMetadataItems,
recordId,
objectPermissionsByObjectMetadataId: {},
}),
).toBeNull(),
);
@ -119,6 +121,7 @@ describe('useDeleteManyRecords', () => {
objectMetadataItem,
record,
}),
objectPermissionsByObjectMetadataId: {},
}),
);
});

View File

@ -5,6 +5,7 @@ import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGq
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import isEmpty from 'lodash.isempty';
import { isDefined } from 'twenty-shared/utils';
@ -35,10 +36,16 @@ export const useAggregateRecords = <T extends AggregateRecordsData>({
recordGqlFieldsAggregate,
});
const objectPermissions = useObjectPermissionsForObject(
objectMetadataItem.id,
);
const hasReadPermission = objectPermissions.canReadObjectRecords;
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
aggregateQuery,
{
skip: skip || !objectMetadataItem,
skip: skip || !objectMetadataItem || !hasReadPermission,
variables: {
filter,
},

View File

@ -5,6 +5,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from 'twenty-shared/utils';
@ -61,7 +62,7 @@ export const useAttachRelatedRecordFromRecord = ({
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const updateOneRecordAndAttachRelations = async ({
recordId,
relatedRecordId,
@ -98,6 +99,7 @@ export const useAttachRelatedRecordFromRecord = ({
[fieldOnRelatedObject]: previousRecord,
},
recordGqlFields: gqlFields,
objectPermissionsByObjectMetadataId,
});
}

View File

@ -14,6 +14,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
@ -70,7 +71,7 @@ export const useCreateManyRecords = <
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
@ -118,6 +119,7 @@ export const useCreateManyRecords = <
...baseOptimisticRecordInputCreatedBy,
...recordToCreate,
},
objectPermissionsByObjectMetadataId,
}),
id: idForCreation as string,
};
@ -152,6 +154,7 @@ export const useCreateManyRecords = <
recordsToCreate: recordNodeCreatedInCache,
objectMetadataItems,
shouldMatchRootQueryFilter,
objectPermissionsByObjectMetadataId,
});
}
@ -178,6 +181,7 @@ export const useCreateManyRecords = <
objectMetadataItems,
shouldMatchRootQueryFilter,
checkForRecordInCache: true,
objectPermissionsByObjectMetadataId,
});
},
})

View File

@ -6,9 +6,10 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { capitalize } from 'twenty-shared/utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCreateManyRecordsMutation = ({
objectNameSingular,
@ -21,6 +22,8 @@ export const useCreateManyRecordsMutation = ({
objectNameSingular,
});
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
if (isUndefinedOrNull(objectMetadataItem)) {
@ -42,6 +45,7 @@ export const useCreateManyRecordsMutation = ({
objectMetadataItems,
objectMetadataItem,
recordGqlFields,
objectPermissionsByObjectMetadataId,
},
)}
}`;

View File

@ -14,6 +14,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
@ -62,6 +63,7 @@ export const useCreateOneRecord = <
);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
@ -90,6 +92,7 @@ export const useCreateOneRecord = <
...recordInput,
id: idForCreation,
},
objectPermissionsByObjectMetadataId,
});
const recordCreatedInCache = createOneRecordInCache({
...optimisticRecordInput,
@ -113,6 +116,7 @@ export const useCreateOneRecord = <
recordsToCreate: [optimisticRecordNode],
objectMetadataItems,
shouldMatchRootQueryFilter,
objectPermissionsByObjectMetadataId,
});
}
}
@ -136,6 +140,7 @@ export const useCreateOneRecord = <
objectMetadataItems,
shouldMatchRootQueryFilter,
checkForRecordInCache: true,
objectPermissionsByObjectMetadataId,
});
}

View File

@ -7,9 +7,10 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { capitalize } from 'twenty-shared/utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCreateOneRecordMutation = ({
objectNameSingular,
@ -30,6 +31,8 @@ export const useCreateOneRecordMutation = ({
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
if (isUndefinedOrNull(objectMetadataItem)) {
return { createOneRecordMutation: EMPTY_MUTATION };
}
@ -46,6 +49,7 @@ export const useCreateOneRecordMutation = ({
objectMetadataItems,
objectMetadataItem,
recordGqlFields: appliedRecordGqlFields,
objectPermissionsByObjectMetadataId,
})}
}
`;

View File

@ -11,12 +11,13 @@ import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordF
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
import { useRecoilValue } from 'recoil';
import { sleep } from '~/utils/sleep';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
type useDeleteManyRecordProps = {
objectNameSingular: string;
@ -52,7 +53,7 @@ export const useDeleteManyRecords = ({
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
@ -115,6 +116,7 @@ export const useDeleteManyRecords = ({
cache: apolloClient.cache,
record: computedOptimisticRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
computedOptimisticRecordsNode.push(optimisticRecordNode);
@ -156,6 +158,7 @@ export const useDeleteManyRecords = ({
cache: apolloClient.cache,
record: { ...cachedRecord, deletedAt: null },
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
const cachedRecordWithConnection =

View File

@ -9,6 +9,7 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField';
@ -37,7 +38,7 @@ export const useDeleteOneRecord = ({
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
@ -84,6 +85,7 @@ export const useDeleteOneRecord = ({
cache: apolloClient.cache,
record: computedOptimisticRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
@ -133,6 +135,7 @@ export const useDeleteOneRecord = ({
deletedAt: null,
},
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
@ -156,6 +159,7 @@ export const useDeleteOneRecord = ({
mutationResponseField,
objectMetadataItem,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
refetchAggregateQueries,
],
);

View File

@ -8,12 +8,13 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
import { useDestroyManyRecordsMutation } from '@/object-record/hooks/useDestroyManyRecordsMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField';
import { useRecoilValue } from 'recoil';
import { sleep } from '~/utils/sleep';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
type useDestroyManyRecordProps = {
objectNameSingular: string;
@ -47,7 +48,7 @@ export const useDestroyManyRecords = ({
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
@ -120,6 +121,7 @@ export const useDestroyManyRecords = ({
objectMetadataItem,
recordsToCreate: cachedRecords,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
}
throw error;

View File

@ -7,6 +7,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField';
import { capitalize, isDefined } from 'twenty-shared/utils';
@ -31,7 +32,7 @@ export const useDestroyOneRecord = ({
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const mutationResponseField =
getDestroyOneRecordMutationResponseField(objectNameSingular);
@ -73,6 +74,7 @@ export const useDestroyOneRecord = ({
objectMetadataItem,
recordsToCreate: [originalRecord],
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
}
@ -89,6 +91,7 @@ export const useDestroyOneRecord = ({
objectMetadataItem,
objectNameSingular,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
],
);

View File

@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
import { capitalize } from 'twenty-shared/utils';
@ -17,6 +18,8 @@ export const useFindDuplicateRecordsQuery = ({
objectNameSingular,
});
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const findDuplicateRecordsQuery = gql`
@ -30,6 +33,7 @@ export const useFindDuplicateRecordsQuery = ({
node ${mapObjectMetadataToGraphQLQuery({
objectMetadataItems,
objectMetadataItem,
objectPermissionsByObjectMetadataId,
})}
cursor
}

View File

@ -9,6 +9,7 @@ import { useFetchMoreRecordsWithPagination } from '@/object-record/hooks/useFetc
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted';
import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier';
@ -68,9 +69,15 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
or: [{ deletedAt: { is: 'NULL' } }, { deletedAt: { is: 'NOT_NULL' } }],
};
const objectPermissions = useObjectPermissionsForObject(
objectMetadataItem.id,
);
const hasReadPermission = objectPermissions.canReadObjectRecords;
const { data, loading, error, fetchMore } =
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
skip: skip || !objectMetadataItem,
skip: skip || !objectMetadataItem || !hasReadPermission,
variables: {
filter: withSoftDeleted
? {

View File

@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import {
generateFindManyRecordsQuery,
QueryCursorDirection,
@ -25,12 +26,15 @@ export const useFindManyRecordsQuery = ({
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const findManyRecordsQuery = generateFindManyRecordsQuery({
objectMetadataItem,
objectMetadataItems,
recordGqlFields,
computeReferences,
cursorDirection,
objectPermissionsByObjectMetadataId,
});
return {

View File

@ -8,6 +8,7 @@ import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isDefined } from 'twenty-shared/utils';
@ -38,10 +39,16 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
withSoftDeleted,
});
const objectPermissions = useObjectPermissionsForObject(
objectMetadataItem.id,
);
const hasReadPermission = objectPermissions.canReadObjectRecords;
const { data, loading, error } = useQuery<{
[nameSingular: string]: RecordGqlNode;
}>(findOneRecordQuery, {
skip: !objectMetadataItem || !objectRecordId || skip,
skip: !objectMetadataItem || !objectRecordId || skip || !hasReadPermission,
variables: { objectRecordId },
onCompleted: (data) => {
const recordWithoutConnection = getRecordFromRecordNode<T>({

View File

@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { capitalize } from 'twenty-shared/utils';
export const useFindOneRecordQuery = ({
@ -22,6 +23,8 @@ export const useFindOneRecordQuery = ({
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const findOneRecordQuery = gql`
query FindOne${capitalize(
objectMetadataItem.nameSingular,
@ -44,6 +47,7 @@ export const useFindOneRecordQuery = ({
objectMetadataItems,
objectMetadataItem,
recordGqlFields,
objectPermissionsByObjectMetadataId,
})}
},
`;

View File

@ -8,6 +8,7 @@ import { UseFindManyRecordsParams } from '@/object-record/hooks/useFindManyRecor
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { cursorFamilyState } from '@/object-record/states/cursorFamilyState';
import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
@ -55,6 +56,12 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
onCompleted,
});
const objectPermissions = useObjectPermissionsForObject(
objectMetadataItem.id,
);
const hasReadPermission = objectPermissions.canReadObjectRecords;
const [findManyRecords, { data, loading, error, fetchMore }] =
useLazyQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
variables: {
@ -83,6 +90,20 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
const findManyRecordsLazy = useRecoilCallback(
({ set }) =>
async () => {
if (!hasReadPermission) {
set(hasNextPageFamilyState(queryIdentifier), false);
set(cursorFamilyState(queryIdentifier), '');
onCompleted?.([]);
return {
data: null,
loading: false,
error: undefined,
called: true,
};
}
const result = await findManyRecords();
const hasNextPage =
@ -98,19 +119,26 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
return result;
},
[queryIdentifier, findManyRecords, objectMetadataItem],
[
hasReadPermission,
findManyRecords,
objectMetadataItem.namePlural,
queryIdentifier,
onCompleted,
],
);
return {
objectMetadataItem,
records,
totalCount,
loading,
error,
loading: hasReadPermission ? loading : false,
error: hasReadPermission ? error : undefined,
fetchMore,
fetchMoreRecords,
queryStateIdentifier: queryIdentifier,
findManyRecords: findManyRecordsLazy,
hasNextPage,
hasReadPermission,
};
};

View File

@ -0,0 +1,32 @@
import { useRecoilValue } from 'recoil';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission } from '~/generated-metadata/graphql';
type useObjectPermissionsReturnType = {
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
};
export const useObjectPermissions = (): useObjectPermissionsReturnType => {
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
const objectPermissions = currentUserWorkspace?.objectPermissions;
if (!isDefined(objectPermissions)) {
return {
objectPermissionsByObjectMetadataId: {},
};
}
const objectPermissionsByObjectMetadataId = objectPermissions?.reduce(
(acc: Record<string, ObjectPermission>, objectPermission) => {
acc[objectPermission.objectMetadataId] = objectPermission;
return acc;
},
{},
);
return {
objectPermissionsByObjectMetadataId,
};
};

View File

@ -0,0 +1,15 @@
import { getObjectPermissionsForObject } from '~/modules/object-metadata/utils/getObjectPermissionsForObject';
import { useMemo } from 'react';
import { useObjectPermissions } from './useObjectPermissions';
export const useObjectPermissionsForObject = (objectMetadataId: string) => {
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
return useMemo(() => {
return getObjectPermissionsForObject(
objectPermissionsByObjectMetadataId,
objectMetadataId,
);
}, [objectPermissionsByObjectMetadataId, objectMetadataId]);
};

View File

@ -9,12 +9,13 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField';
import { useRecoilValue } from 'recoil';
import { sleep } from '~/utils/sleep';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
type useRestoreManyRecordProps = {
objectNameSingular: string;
@ -50,7 +51,7 @@ export const useRestoreManyRecords = ({
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const mutationResponseField = getRestoreManyRecordsMutationResponseField(
objectMetadataItem.namePlural,
);
@ -111,6 +112,7 @@ export const useRestoreManyRecords = ({
cache: apolloClient.cache,
record: computedOptimisticRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
cache: apolloClient.cache,
@ -169,6 +171,7 @@ export const useRestoreManyRecords = ({
cache: apolloClient.cache,
record: cachedRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({

View File

@ -10,6 +10,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
@ -57,6 +58,7 @@ export const useUpdateOneRecord = <
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
@ -75,6 +77,7 @@ export const useUpdateOneRecord = <
recordInput: updateOneRecordInput,
cache: apolloClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
const cachedRecord = getRecordFromCache<ObjectRecord>(idToUpdate);
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
@ -118,6 +121,7 @@ export const useUpdateOneRecord = <
cache: apolloClient.cache,
record: computedOptimisticRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
@ -190,6 +194,7 @@ export const useUpdateOneRecord = <
),
},
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({

View File

@ -7,6 +7,7 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
import { capitalize } from 'twenty-shared/utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@ -26,6 +27,8 @@ export const useUpdateOneRecordMutation = ({
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
if (isUndefinedOrNull(objectMetadataItem)) {
return { updateOneRecordMutation: EMPTY_MUTATION };
}
@ -44,14 +47,15 @@ export const useUpdateOneRecordMutation = ({
const updateOneRecordMutation = gql`
mutation UpdateOne${capitalizedObjectName}($idToUpdate: UUID!, $input: ${capitalizedObjectName}UpdateInput!) {
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
{
objectMetadataItems,
objectMetadataItem,
computeReferences,
recordGqlFields: appliedRecordGqlFields,
},
)}
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
{
objectMetadataItems,
objectMetadataItem,
computeReferences,
recordGqlFields: appliedRecordGqlFields,
objectPermissionsByObjectMetadataId,
},
)}
}
`;