[FE] handle restricted objects 2 (#12437)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -2960,7 +2960,7 @@ export type OnDbEventSubscriptionVariables = Exact<{
|
||||
|
||||
export type OnDbEventSubscription = { __typename?: 'Subscription', onDbEvent: { __typename?: 'OnDbEventDTO', eventDate: string, action: DatabaseEventAction, objectNameSingular: string, updatedFields?: Array<string> | null, record: any } };
|
||||
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
|
||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@ -2977,7 +2977,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingPermissionType> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
|
||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||
workflowVersionId: Scalars['String'];
|
||||
@ -3216,15 +3216,6 @@ export const AvailableSsoIdentityProvidersFragmentFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ObjectPermissionFragmentFragmentDoc = gql`
|
||||
fragment ObjectPermissionFragment on ObjectPermission {
|
||||
objectMetadataId
|
||||
canReadObjectRecords
|
||||
canUpdateObjectRecords
|
||||
canSoftDeleteObjectRecords
|
||||
canDestroyObjectRecords
|
||||
}
|
||||
`;
|
||||
export const SettingPermissionFragmentFragmentDoc = gql`
|
||||
fragment SettingPermissionFragment on SettingPermission {
|
||||
id
|
||||
@ -3259,6 +3250,15 @@ export const DeletedWorkspaceMemberQueryFragmentFragmentDoc = gql`
|
||||
userEmail
|
||||
}
|
||||
`;
|
||||
export const ObjectPermissionFragmentFragmentDoc = gql`
|
||||
fragment ObjectPermissionFragment on ObjectPermission {
|
||||
objectMetadataId
|
||||
canReadObjectRecords
|
||||
canUpdateObjectRecords
|
||||
canSoftDeleteObjectRecords
|
||||
canDestroyObjectRecords
|
||||
}
|
||||
`;
|
||||
export const RoleFragmentFragmentDoc = gql`
|
||||
fragment RoleFragment on Role {
|
||||
id
|
||||
@ -3295,6 +3295,9 @@ export const UserQueryFragmentFragmentDoc = gql`
|
||||
currentUserWorkspace {
|
||||
settingsPermissions
|
||||
objectRecordsPermissions
|
||||
objectPermissions {
|
||||
...ObjectPermissionFragment
|
||||
}
|
||||
}
|
||||
currentWorkspace {
|
||||
id
|
||||
@ -3364,6 +3367,7 @@ export const UserQueryFragmentFragmentDoc = gql`
|
||||
}
|
||||
${WorkspaceMemberQueryFragmentFragmentDoc}
|
||||
${DeletedWorkspaceMemberQueryFragmentFragmentDoc}
|
||||
${ObjectPermissionFragmentFragmentDoc}
|
||||
${RoleFragmentFragmentDoc}`;
|
||||
export const GetTimelineCalendarEventsFromCompanyIdDocument = gql`
|
||||
query GetTimelineCalendarEventsFromCompanyId($companyId: UUID!, $page: Int!, $pageSize: Int!) {
|
||||
|
||||
@ -66,8 +66,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
position: 0,
|
||||
isPinned: true,
|
||||
Icon: IconPlus,
|
||||
shouldBeRegistered: ({ hasObjectReadOnlyPermission }) =>
|
||||
!hasObjectReadOnlyPermission,
|
||||
shouldBeRegistered: ({ objectPermissions }) =>
|
||||
objectPermissions.canUpdateObjectRecords,
|
||||
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
|
||||
component: <CreateNewTableRecordNoSelectionRecordAction />,
|
||||
},
|
||||
@ -194,10 +194,15 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
Icon: IconTrash,
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({ selectedRecord, isSoftDeleteFilterActive }) =>
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
isSoftDeleteFilterActive,
|
||||
objectPermissions,
|
||||
}) =>
|
||||
isDefined(selectedRecord) &&
|
||||
!selectedRecord.isRemote &&
|
||||
!isSoftDeleteFilterActive,
|
||||
!isSoftDeleteFilterActive &&
|
||||
objectPermissions.canSoftDeleteObjectRecords,
|
||||
availableOn: [
|
||||
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||
ActionViewType.SHOW_PAGE,
|
||||
@ -215,12 +220,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
hasObjectReadOnlyPermission,
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
isSoftDeleteFilterActive,
|
||||
numberOfSelectedRecords,
|
||||
}) =>
|
||||
!hasObjectReadOnlyPermission &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
!isRemote &&
|
||||
!isSoftDeleteFilterActive &&
|
||||
isDefined(numberOfSelectedRecords) &&
|
||||
@ -268,12 +273,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
Icon: IconTrashX,
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
hasObjectReadOnlyPermission,
|
||||
isRemote,
|
||||
}) =>
|
||||
!hasObjectReadOnlyPermission &&
|
||||
shouldBeRegistered: ({ selectedRecord, objectPermissions, isRemote }) =>
|
||||
objectPermissions.canDestroyObjectRecords &&
|
||||
!isRemote &&
|
||||
isDefined(selectedRecord?.deletedAt),
|
||||
availableOn: [
|
||||
@ -317,12 +318,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
accent: 'danger',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
hasObjectReadOnlyPermission,
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
isSoftDeleteFilterActive,
|
||||
numberOfSelectedRecords,
|
||||
}) =>
|
||||
!hasObjectReadOnlyPermission &&
|
||||
objectPermissions.canDestroyObjectRecords &&
|
||||
!isRemote &&
|
||||
isDefined(isSoftDeleteFilterActive) &&
|
||||
isSoftDeleteFilterActive &&
|
||||
@ -343,14 +344,14 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
selectedRecord,
|
||||
hasObjectReadOnlyPermission,
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
isShowPage,
|
||||
isSoftDeleteFilterActive,
|
||||
}) =>
|
||||
!isRemote &&
|
||||
isDefined(selectedRecord?.deletedAt) &&
|
||||
!hasObjectReadOnlyPermission &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
((isDefined(isShowPage) && isShowPage) ||
|
||||
(isDefined(isSoftDeleteFilterActive) && isSoftDeleteFilterActive)),
|
||||
availableOn: [
|
||||
@ -370,12 +371,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
||||
accent: 'default',
|
||||
isPinned: true,
|
||||
shouldBeRegistered: ({
|
||||
hasObjectReadOnlyPermission,
|
||||
objectPermissions,
|
||||
isRemote,
|
||||
isSoftDeleteFilterActive,
|
||||
numberOfSelectedRecords,
|
||||
}) =>
|
||||
!hasObjectReadOnlyPermission &&
|
||||
objectPermissions.canSoftDeleteObjectRecords &&
|
||||
!isRemote &&
|
||||
isDefined(isSoftDeleteFilterActive) &&
|
||||
isSoftDeleteFilterActive &&
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectPermissions } from '@/object-record/cache/types/ObjectPermissions';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||
|
||||
export type ShouldBeRegisteredFunctionParams = {
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
hasObjectReadOnlyPermission?: boolean;
|
||||
objectPermissions: ObjectPermissions;
|
||||
isWorkflowEnabled: boolean;
|
||||
recordFilters?: RecordFilter[];
|
||||
isShowPage?: boolean;
|
||||
|
||||
@ -8,9 +8,9 @@ import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useContext } from 'react';
|
||||
@ -42,7 +42,9 @@ export const useShouldActionBeRegisteredParams = ({
|
||||
const selectedRecord =
|
||||
useRecoilValue(recordStoreFamilyState(recordId ?? '')) || undefined;
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem?.id,
|
||||
);
|
||||
|
||||
const isNoteOrTask =
|
||||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note ||
|
||||
@ -78,7 +80,7 @@ export const useShouldActionBeRegisteredParams = ({
|
||||
return {
|
||||
objectMetadataItem,
|
||||
isFavorite,
|
||||
hasObjectReadOnlyPermission,
|
||||
objectPermissions,
|
||||
isNoteOrTask,
|
||||
isInRightDrawer,
|
||||
isSoftDeleteFilterActive,
|
||||
|
||||
@ -67,7 +67,10 @@ export const ActivityRichTextEditor = ({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
});
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({ recordId: activityId });
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: activityId,
|
||||
objectMetadataId: objectMetadataItemActivity.id,
|
||||
});
|
||||
|
||||
const isReadOnly = isFieldValueReadOnly({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
|
||||
@ -7,8 +7,11 @@ import { DropZone } from '@/activities/files/components/DropZone';
|
||||
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
@ -17,8 +20,6 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
const StyledAttachmentsContainer = styled.div`
|
||||
display: flex;
|
||||
@ -47,8 +48,6 @@ export const Attachments = ({
|
||||
|
||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]);
|
||||
};
|
||||
@ -63,6 +62,16 @@ export const Attachments = ({
|
||||
|
||||
const isAttachmentsEmpty = !attachments || attachments.length === 0;
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (loading && isAttachmentsEmpty) {
|
||||
return <SkeletonLoader />;
|
||||
}
|
||||
@ -94,7 +103,7 @@ export const Attachments = ({
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add file"
|
||||
@ -120,7 +129,7 @@ export const Attachments = ({
|
||||
title="All"
|
||||
attachments={attachments ?? []}
|
||||
button={
|
||||
!hasObjectReadOnlyPermission && (
|
||||
!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
|
||||
@ -12,9 +12,10 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
export const usePrepareFindManyActivitiesQuery = ({
|
||||
activityObjectNameSingular,
|
||||
@ -32,6 +33,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
||||
|
||||
const cache = useApolloClient().cache;
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { upsertFindManyRecordsQueryInCache: upsertFindManyActivitiesInCache } =
|
||||
useUpsertFindManyRecordsQueryInCache({
|
||||
@ -64,6 +66,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
||||
objectMetadataItem: targetableObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const activityTargets: (TaskTarget | NoteTarget)[] =
|
||||
|
||||
@ -3,9 +3,12 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
||||
import { NoteList } from '@/activities/notes/components/NoteList';
|
||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
@ -14,8 +17,6 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
const StyledNotesContainer = styled.div`
|
||||
display: flex;
|
||||
@ -32,14 +33,22 @@ export const Notes = ({
|
||||
}) => {
|
||||
const { notes, loading } = useNotes(targetableObject);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
|
||||
const isNotesEmpty = !notes || notes.length === 0;
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (loading && isNotesEmpty) {
|
||||
return <SkeletonLoader />;
|
||||
}
|
||||
@ -59,7 +68,7 @@ export const Notes = ({
|
||||
There are no associated notes with this record.
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New note"
|
||||
@ -81,7 +90,7 @@ export const Notes = ({
|
||||
title="All"
|
||||
notes={notes}
|
||||
button={
|
||||
!hasObjectReadOnlyPermission && (
|
||||
!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
|
||||
@ -1,27 +1,31 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
|
||||
export const AddTaskButton = ({
|
||||
activityTargetableObjects,
|
||||
activityTargetableObject,
|
||||
}: {
|
||||
activityTargetableObjects?: ActivityTargetableObject[];
|
||||
activityTargetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: activityTargetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
if (
|
||||
!isNonEmptyArray(activityTargetableObjects) ||
|
||||
hasObjectReadOnlyPermission
|
||||
) {
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (!hasObjectUpdatePermissions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -33,7 +37,7 @@ export const AddTaskButton = ({
|
||||
title="Add task"
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: activityTargetableObjects,
|
||||
targetableObjects: [activityTargetableObject],
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
@ -22,7 +22,7 @@ export const ObjectTasks = ({ targetableObject }: ObjectTasksProps) => {
|
||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'entity-tasks-filter-scope' }}
|
||||
>
|
||||
<TaskGroups targetableObjects={[targetableObject]} />
|
||||
<TaskGroups targetableObject={targetableObject} />
|
||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
|
||||
export const PageAddTaskButton = () => {
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
// TODO: fetch workspace member from filter here
|
||||
|
||||
const handleClick = () => {
|
||||
openCreateActivity({
|
||||
targetableObjects: [],
|
||||
});
|
||||
};
|
||||
|
||||
return <PageAddButton onClick={handleClick} />;
|
||||
};
|
||||
@ -5,13 +5,14 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
||||
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { AddTaskButton } from './AddTaskButton';
|
||||
import { TaskList } from './TaskList';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
@ -20,8 +21,8 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { AddTaskButton } from './AddTaskButton';
|
||||
import { TaskList } from './TaskList';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -31,15 +32,23 @@ const StyledContainer = styled.div`
|
||||
|
||||
type TaskGroupsProps = {
|
||||
filterDropdownId?: string;
|
||||
targetableObjects?: ActivityTargetableObject[];
|
||||
targetableObject: ActivityTargetableObject;
|
||||
};
|
||||
|
||||
export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
export const TaskGroups = ({ targetableObject }: TaskGroupsProps) => {
|
||||
const { tasks, tasksLoading } = useTasks({
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
targetableObjects: [targetableObject],
|
||||
});
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
@ -74,14 +83,14 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
All tasks addressed. Maintain the momentum.
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{!hasObjectUpdatePermissions && (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="New task"
|
||||
variant={'secondary'}
|
||||
onClick={() =>
|
||||
openCreateActivity({
|
||||
targetableObjects: targetableObjects ?? [],
|
||||
targetableObjects: [targetableObject],
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -107,7 +116,7 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
tasks={tasksByStatus}
|
||||
button={
|
||||
(status === 'TODO' || !hasTodoStatus) && (
|
||||
<AddTaskButton activityTargetableObjects={targetableObjects} />
|
||||
<AddTaskButton activityTargetableObject={targetableObject} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -42,12 +42,10 @@ export const Empty: Story = {};
|
||||
|
||||
export const WithTasks: Story = {
|
||||
args: {
|
||||
targetableObjects: [
|
||||
{
|
||||
id: mockedTasks[0].taskTargets?.[0].personId,
|
||||
targetObjectNameSingular: 'person',
|
||||
},
|
||||
] as ActivityTargetableObject[],
|
||||
targetableObject: {
|
||||
id: mockedTasks[0].taskTargets?.[0].personId,
|
||||
targetObjectNameSingular: 'person',
|
||||
} as ActivityTargetableObject,
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
|
||||
@ -13,8 +13,9 @@ import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRec
|
||||
import { encodeCursor } from '@/apollo/utils/encodeCursor';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
|
||||
|
||||
/*
|
||||
TODO: for now new records are added to all cached record lists, no matter what the variables (filters, orderBy, etc.) are.
|
||||
@ -28,6 +29,7 @@ type TriggerCreateRecordsOptimisticEffectArgs = {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
shouldMatchRootQueryFilter?: boolean;
|
||||
checkForRecordInCache?: boolean;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
};
|
||||
export const triggerCreateRecordsOptimisticEffect = ({
|
||||
cache,
|
||||
@ -36,6 +38,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
objectMetadataItems,
|
||||
shouldMatchRootQueryFilter,
|
||||
checkForRecordInCache = false,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: TriggerCreateRecordsOptimisticEffectArgs) => {
|
||||
const getRecordNodeFromCache = (recordId: string): RecordGqlNode | null => {
|
||||
const cachedRecord = getRecordFromCache({
|
||||
@ -43,6 +46,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordId,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
return getRecordNodeFromRecord({
|
||||
objectMetadataItem,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { UserWorkspace } from '~/generated/graphql';
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
import { UserWorkspace } from '~/generated/graphql';
|
||||
|
||||
export type CurrentUserWorkspace = Pick<
|
||||
UserWorkspace,
|
||||
'settingsPermissions' | 'objectRecordsPermissions'
|
||||
'settingsPermissions' | 'objectRecordsPermissions' | 'objectPermissions'
|
||||
>;
|
||||
|
||||
export const currentUserWorkspaceState =
|
||||
|
||||
@ -3,6 +3,7 @@ import { commandMenuNavigationRecordsState } from '@/command-menu/states/command
|
||||
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
@ -18,6 +19,7 @@ export const CommandMenuContextChipRecordSetterEffect = () => {
|
||||
);
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const commandMenuNavigationStack = useRecoilValue(
|
||||
commandMenuNavigationStackState,
|
||||
@ -46,6 +48,7 @@ export const CommandMenuContextChipRecordSetterEffect = () => {
|
||||
cache: apolloClient.cache,
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
@ -68,6 +71,7 @@ export const CommandMenuContextChipRecordSetterEffect = () => {
|
||||
commandMenuNavigationStack.length,
|
||||
objectMetadataItems,
|
||||
setCommandMenuNavigationRecords,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
||||
@ -50,6 +50,7 @@ export const initialFavorites: Favorite[] = [
|
||||
key: mockId,
|
||||
labelIdentifier: 'favoriteLabel',
|
||||
avatarUrl: 'example.com',
|
||||
company: { id: '5', name: 'Company Test 2' },
|
||||
avatarType: 'squared' as AvatarType,
|
||||
link: 'example.com',
|
||||
recordId: '1',
|
||||
@ -86,17 +87,17 @@ export const sortedFavorites = [
|
||||
__typename: 'Favorite',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
position: 2,
|
||||
key: '8f3b2121-f194-4ba4-9fbf-2d5a37126806',
|
||||
labelIdentifier: 'favoriteLabel',
|
||||
avatarUrl: 'example.com',
|
||||
link: 'example.com',
|
||||
recordId: '1',
|
||||
__typename: 'Favorite',
|
||||
avatarType: 'squared',
|
||||
avatarUrl: undefined,
|
||||
favoriteFolderId: '1',
|
||||
forWorkspaceMemberId: '1',
|
||||
__typename: 'Favorite',
|
||||
id: '3',
|
||||
labelIdentifier: 'Company Test 2',
|
||||
link: '/object/company/5',
|
||||
objectNameSingular: 'company',
|
||||
position: 2,
|
||||
recordId: '5',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -95,9 +95,8 @@ export const sortFavorites = (
|
||||
} as ProcessedFavorite;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...favorite,
|
||||
} as ProcessedFavorite;
|
||||
return null;
|
||||
})
|
||||
.filter(isDefined)
|
||||
.sort((a, b) => a.position - b.position);
|
||||
};
|
||||
|
||||
@ -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}
|
||||
}`;
|
||||
};
|
||||
|
||||
@ -8,6 +8,7 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { prefillRecord } from '@/object-record/utils/prefillRecord';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
@ -21,6 +22,8 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
return (record: ObjectRecord) => {
|
||||
@ -42,6 +45,7 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
|
||||
objectMetadataItem,
|
||||
computeReferences: true,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}
|
||||
`;
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const useGetRecordFromCache = ({
|
||||
@ -24,6 +25,7 @@ export const useGetRecordFromCache = ({
|
||||
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
@ -38,6 +40,7 @@ export const useGetRecordFromCache = ({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
recordGqlFields: appliedRecordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
},
|
||||
[
|
||||
@ -45,6 +48,7 @@ export const useGetRecordFromCache = ({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
appliedRecordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -18,6 +19,8 @@ export const useReadFindManyRecordsQueryInCache = ({
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const readFindManyRecordsQueryInCache = <
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
>({
|
||||
@ -31,6 +34,7 @@ export const useReadFindManyRecordsQueryInCache = ({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const existingRecordsQueryResult =
|
||||
|
||||
@ -5,6 +5,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||
|
||||
@ -16,6 +17,7 @@ export const useUpsertFindManyRecordsQueryInCache = ({
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const upsertFindManyRecordsQueryInCache = <
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
@ -35,6 +37,7 @@ export const useUpsertFindManyRecordsQueryInCache = ({
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const newObjectRecordConnection = getRecordConnectionFromRecords({
|
||||
|
||||
8
packages/twenty-front/src/modules/object-record/cache/types/ObjectPermissions.ts
vendored
Normal file
8
packages/twenty-front/src/modules/object-record/cache/types/ObjectPermissions.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
|
||||
export type ObjectPermissions = {
|
||||
[K in keyof Omit<
|
||||
ObjectPermission,
|
||||
'objectMetadataId' | '__typename'
|
||||
>]-?: boolean;
|
||||
};
|
||||
@ -6,9 +6,10 @@ import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFr
|
||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
export type GetRecordFromCacheArgs = {
|
||||
cache: ApolloCache<object>;
|
||||
@ -16,6 +17,7 @@ export type GetRecordFromCacheArgs = {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
recordGqlFields?: RecordGqlFields;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
};
|
||||
export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataItem,
|
||||
@ -23,6 +25,7 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
||||
cache,
|
||||
recordId,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: GetRecordFromCacheArgs) => {
|
||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||
return null;
|
||||
@ -39,6 +42,7 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
recordGqlFields: appliedRecordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
},
|
||||
)}
|
||||
`;
|
||||
|
||||
@ -7,6 +7,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
|
||||
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const updateRecordFromCache = <T extends ObjectRecord>({
|
||||
@ -15,12 +16,14 @@ export const updateRecordFromCache = <T extends ObjectRecord>({
|
||||
cache,
|
||||
recordGqlFields,
|
||||
record,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
cache: ApolloCache<object>;
|
||||
recordGqlFields: Record<string, boolean>;
|
||||
record: T;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
}) => {
|
||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||
return null;
|
||||
@ -35,6 +38,7 @@ export const updateRecordFromCache = <T extends ObjectRecord>({
|
||||
objectMetadataItem,
|
||||
computeReferences: true,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
},
|
||||
)}
|
||||
`;
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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: {},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
@ -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,
|
||||
},
|
||||
)}
|
||||
}`;
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
})}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
? {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>({
|
||||
|
||||
@ -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,
|
||||
})}
|
||||
},
|
||||
`;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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]);
|
||||
};
|
||||
@ -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({
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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,
|
||||
},
|
||||
)}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@ -6,9 +6,10 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
|
||||
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||
|
||||
export const useGenerateCombinedFindManyRecordsQuery = ({
|
||||
operationSignatures,
|
||||
@ -16,6 +17,7 @@ export const useGenerateCombinedFindManyRecordsQuery = ({
|
||||
operationSignatures: RecordGqlOperationSignature[];
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
if (!isNonEmptyArray(operationSignatures)) {
|
||||
return null;
|
||||
@ -92,6 +94,7 @@ export const useGenerateCombinedFindManyRecordsQuery = ({
|
||||
generateDepthOneRecordGqlFields({
|
||||
objectMetadataItem,
|
||||
}),
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}
|
||||
cursor
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getR
|
||||
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { CombinedFindManyRecordsQueryResult } from '@/object-record/multiple-objects/types/CombinedFindManyRecordsQueryResult';
|
||||
import { generateCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/utils/generateCombinedFindManyRecordsQueryVariables';
|
||||
import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
|
||||
@ -18,6 +19,8 @@ export const usePerformCombinedFindManyRecords = () => {
|
||||
const client = useApolloClient();
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const generateCombinedFindManyRecordsQuery = (
|
||||
operationSignatures: RecordGqlOperationSignature[],
|
||||
objectMetadataItemsValue: ObjectMetadataItem[],
|
||||
@ -93,6 +96,7 @@ export const usePerformCombinedFindManyRecords = () => {
|
||||
generateDepthOneRecordGqlFields({
|
||||
objectMetadataItem,
|
||||
}),
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}
|
||||
cursor
|
||||
}
|
||||
|
||||
@ -92,6 +92,7 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
indexIdentifierUrl: () => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: 'companies',
|
||||
|
||||
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||
import { RecordBoardCardFocusHotkeyEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect';
|
||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||
@ -23,8 +24,11 @@ export const RecordBoardCardDraggableContainer = ({
|
||||
recordId: string;
|
||||
rowIndex: number;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const { columnIndex } = useContext(RecordBoardColumnContext);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useContext, useState } from 'react';
|
||||
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
||||
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
||||
@ -9,7 +10,6 @@ import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-
|
||||
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
||||
@ -95,7 +95,11 @@ export const RecordBoardColumnHeader = () => {
|
||||
const { aggregateValue, aggregateLabel } =
|
||||
useAggregateRecordsForRecordBoardColumn();
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem: objectMetadataItem,
|
||||
@ -143,7 +147,7 @@ export const RecordBoardColumnHeader = () => {
|
||||
Icon={IconDotsVertical}
|
||||
onClick={handleBoardColumnMenuOpen}
|
||||
/>
|
||||
{!hasObjectReadOnlyPermission && (
|
||||
{hasObjectUpdatePermissions && (
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconPlus}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useContext } from 'react';
|
||||
@ -32,13 +32,17 @@ export const RecordBoardColumnNewRecordButton = () => {
|
||||
|
||||
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem: objectMetadataItem,
|
||||
});
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
if (!hasObjectUpdatePermissions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { ActorFieldDisplay } from '@/object-record/record-field/meta-types/displ
|
||||
import { ArrayFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ArrayFieldDisplay';
|
||||
import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/display/components/BooleanFieldDisplay';
|
||||
import { EmailsFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailsFieldDisplay';
|
||||
import { ForbiddenFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ForbiddenFieldDisplay';
|
||||
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
|
||||
import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhonesFieldDisplay';
|
||||
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
||||
@ -22,6 +23,7 @@ import { isFieldRelationFromManyObjects } from '@/object-record/record-field/typ
|
||||
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||
@ -49,13 +51,18 @@ import { isFieldText } from '../types/guards/isFieldText';
|
||||
import { isFieldUuid } from '../types/guards/isFieldUuid';
|
||||
|
||||
export const FieldDisplay = () => {
|
||||
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
|
||||
const { fieldDefinition, isLabelIdentifier, isForbidden } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const isChipDisplay = isFieldIdentifierDisplay(
|
||||
fieldDefinition,
|
||||
isLabelIdentifier,
|
||||
);
|
||||
|
||||
if (isDefined(isForbidden) && isForbidden) {
|
||||
return <ForbiddenFieldDisplay />;
|
||||
}
|
||||
|
||||
return isChipDisplay ? (
|
||||
<ChipFieldDisplay />
|
||||
) : isFieldRelationToOneObject(fieldDefinition) ? (
|
||||
|
||||
@ -38,6 +38,7 @@ export type GenericFieldContextType = {
|
||||
onOpenEditMode?: () => void;
|
||||
onCloseEditMode?: () => void;
|
||||
triggerEvent?: TriggerEventType;
|
||||
isForbidden?: boolean;
|
||||
};
|
||||
|
||||
export const FieldContext = createContext<GenericFieldContextType>(
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UseIsRecordReadOnlyParams = {
|
||||
recordId: string;
|
||||
objectMetadataId: string;
|
||||
};
|
||||
|
||||
export const useIsRecordReadOnly = ({
|
||||
recordId,
|
||||
objectMetadataId,
|
||||
}: UseIsRecordReadOnlyParams) => {
|
||||
const recordDeletedAt = useRecoilValue<ObjectRecord | null>(
|
||||
recordStoreFamilySelector({
|
||||
@ -18,7 +20,9 @@ export const useIsRecordReadOnly = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const objectPermissions = useObjectPermissionsForObject(objectMetadataId);
|
||||
|
||||
return hasObjectReadOnlyPermission || isDefined(recordDeletedAt);
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
return !hasObjectUpdatePermissions || isDefined(recordDeletedAt);
|
||||
};
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
import { Theme, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
const StyledContainer = styled.div<{ theme: Theme }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
background: ${({ theme }) => theme.background.transparent.lighter};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
padding: ${({ theme }) => theme.spacing(1, 2)};
|
||||
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
export const ForbiddenFieldDisplay = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledContainer theme={theme}>
|
||||
<Trans>Forbidden</Trans>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -31,6 +31,7 @@ const meta: Meta = {
|
||||
>
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
indexIdentifierUrl: () => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: CoreObjectNamePlural.Company,
|
||||
|
||||
@ -82,6 +82,7 @@ const RelationToOneFieldInputWithContext = ({
|
||||
relationObjectMetadataNamePlural: 'companies',
|
||||
relationObjectMetadataNameSingular:
|
||||
CoreObjectNameSingular.Company,
|
||||
relationObjectMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
||||
objectMetadataNameSingular: 'person',
|
||||
relationFieldMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
||||
},
|
||||
|
||||
@ -131,6 +131,7 @@ export type FieldRelationMetadata = BaseFieldMetadata & {
|
||||
relationFieldMetadataId: string;
|
||||
relationObjectMetadataNamePlural: string;
|
||||
relationObjectMetadataNameSingular: string;
|
||||
relationObjectMetadataId: string;
|
||||
relationType?: RelationDefinitionType;
|
||||
targetFieldMetadataName?: string;
|
||||
useEditButton?: boolean;
|
||||
|
||||
@ -23,6 +23,8 @@ import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewCompon
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
|
||||
const StyledIndexContainer = styled.div`
|
||||
display: flex;
|
||||
@ -57,10 +59,23 @@ export const RecordIndexContainerGater = () => {
|
||||
recordIndexId,
|
||||
});
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const objectPermissions = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||
|
||||
if (!hasObjectReadPermissions) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId,
|
||||
recordIndexId,
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
import { createRequiredContext } from '~/utils/createRequiredContext';
|
||||
|
||||
export type RecordIndexContextValue = {
|
||||
@ -7,6 +8,7 @@ export type RecordIndexContextValue = {
|
||||
objectNamePlural: string;
|
||||
objectNameSingular: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
recordIndexId: string;
|
||||
};
|
||||
|
||||
|
||||
@ -229,6 +229,7 @@ describe('useRecordData', () => {
|
||||
options: null,
|
||||
placeHolder: 'Last update',
|
||||
relationFieldMetadataId: undefined,
|
||||
relationObjectMetadataId: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationType: undefined,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import {
|
||||
SingleRecordPickerMenuItems,
|
||||
SingleRecordPickerMenuItemsProps,
|
||||
@ -7,7 +9,6 @@ import { useSingleRecordPickerSearch } from '@/object-record/record-picker/singl
|
||||
import { SingleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/single-record-picker/states/contexts/SingleRecordPickerComponentInstanceContext';
|
||||
import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState';
|
||||
import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@ -48,8 +49,6 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
||||
SingleRecordPickerComponentInstanceContext,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const recordPickerSearchFilter = useRecoilComponentValueV2(
|
||||
singleRecordPickerSearchFilterComponentState,
|
||||
recordPickerInstanceId,
|
||||
@ -60,6 +59,16 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
||||
excludedRecordIds,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const createNewButton = isDefined(onCreate) && (
|
||||
<CreateNewButton
|
||||
onClick={() => onCreate?.(recordPickerSearchFilter)}
|
||||
@ -72,7 +81,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
||||
<>
|
||||
{layoutDirection === 'search-bar-on-bottom' && (
|
||||
<>
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
{isDefined(onCreate) && !hasObjectUpdatePermissions && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
@ -116,7 +125,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
||||
{records.recordsToSelect.length > 0 && isDefined(onCreate) && (
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
||||
{isDefined(onCreate) && !hasObjectUpdatePermissions && (
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -5,6 +5,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||
@ -39,6 +41,7 @@ export const FieldsCard = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
objectNameSingular,
|
||||
@ -87,11 +90,16 @@ export const FieldsCard = ({
|
||||
fieldMetadataItem.name === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name === 'taskTargets')
|
||||
),
|
||||
) &&
|
||||
getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
fieldMetadataItem.relationDefinition?.targetObjectMetadata.id,
|
||||
).canReadObjectRecords,
|
||||
);
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: objectRecordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
@ -58,6 +59,10 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
@ -65,6 +70,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: objectRecordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const { navigateToIndexView, rankInView, totalCount } =
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||
@ -55,8 +56,13 @@ export const SummaryCard = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: objectRecordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -10,6 +10,7 @@ import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/uti
|
||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import {
|
||||
FieldContext,
|
||||
@ -124,6 +125,10 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const relationObjectPermissions = useObjectPermissionsForObject(
|
||||
relationObjectMetadataItem.id,
|
||||
);
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||
@ -213,6 +218,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: relationRecord.id,
|
||||
objectMetadataId: relationObjectMetadataItem.id,
|
||||
});
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||
@ -254,14 +260,15 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
text="Detach"
|
||||
onClick={handleDetach}
|
||||
/>
|
||||
{!isAccountOwnerRelation && (
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
)}
|
||||
{!isAccountOwnerRelation &&
|
||||
relationObjectPermissions.canSoftDeleteObjectRecords && (
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
accent="danger"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
|
||||
@ -115,6 +115,7 @@ export const RecordDetailRelationSection = ({
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId,
|
||||
objectMetadataId: relationObjectMetadataItem.id,
|
||||
});
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||
|
||||
@ -144,6 +144,7 @@ export const RecordDetailRelationSectionDropdown = ({
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId,
|
||||
objectMetadataId: relationObjectMetadataItem.id,
|
||||
});
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RecordTableBodyEffectsWrapper } from '@/object-record/record-table/components/RecordTableBodyEffectsWrapper';
|
||||
@ -16,7 +17,12 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTable = () => {
|
||||
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
|
||||
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const tableBodyRef = useRef<HTMLTableElement>(null);
|
||||
|
||||
@ -61,12 +67,16 @@ export const RecordTable = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordTableBodyEffectsWrapper
|
||||
hasRecordGroups={hasRecordGroups}
|
||||
tableBodyRef={tableBodyRef}
|
||||
/>
|
||||
<RecordTableScrollToFocusedCellEffect />
|
||||
<RecordTableScrollToFocusedRowEffect />
|
||||
{objectPermissions.canReadObjectRecords && (
|
||||
<>
|
||||
<RecordTableBodyEffectsWrapper
|
||||
hasRecordGroups={hasRecordGroups}
|
||||
tableBodyRef={tableBodyRef}
|
||||
/>
|
||||
<RecordTableScrollToFocusedCellEffect />
|
||||
<RecordTableScrollToFocusedRowEffect />
|
||||
</>
|
||||
)}
|
||||
{recordTableIsEmpty && !hasRecordGroups ? (
|
||||
<RecordTableEmpty tableBodyRef={tableBodyRef} />
|
||||
) : (
|
||||
|
||||
@ -53,6 +53,7 @@ const meta: Meta = {
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
indexIdentifierUrl: (_recordId: string) => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: 'companies',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
||||
@ -6,15 +7,12 @@ import { RecordTableEmptyStateReadOnly } from '@/object-record/record-table/empt
|
||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableEmptyState = () => {
|
||||
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||
const noRecordAtAll = totalCount === 0;
|
||||
|
||||
@ -25,7 +23,13 @@ export const RecordTableEmptyState = () => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (!hasObjectUpdatePermissions) {
|
||||
return <RecordTableEmptyStateReadOnly />;
|
||||
}
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@ import { OnboardingStatus } from '~/generated-metadata/graphql';
|
||||
export const RecordTableRecordGroupBodyEffect = () => {
|
||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||
|
||||
const [hasInitialized, setHasInitialized] = useState(false);
|
||||
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
|
||||
const recordGroupId = useCurrentRecordGroupId();
|
||||
@ -84,8 +86,11 @@ export const RecordTableRecordGroupBodyEffect = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
findManyRecords();
|
||||
}, [onboardingStatus, findManyRecords]);
|
||||
if (!hasInitialized) {
|
||||
findManyRecords();
|
||||
setHasInitialized(true);
|
||||
}
|
||||
}, [onboardingStatus, findManyRecords, hasInitialized]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
|
||||
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
@ -19,7 +22,8 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
useRecordTableRowContextOrThrow();
|
||||
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
||||
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
|
||||
useRecordIndexContextOrThrow();
|
||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||
@ -29,6 +33,28 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
|
||||
const objectPermissions = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
let hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||
|
||||
if (
|
||||
isFieldRelationToOneObject(columnDefinition) ||
|
||||
isFieldRelationFromManyObjects(columnDefinition)
|
||||
) {
|
||||
const relationObjectMetadataId =
|
||||
columnDefinition.metadata.relationObjectMetadataId;
|
||||
|
||||
const relationObjectPermissions = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
relationObjectMetadataId,
|
||||
);
|
||||
|
||||
hasObjectReadPermissions = relationObjectPermissions.canReadObjectRecords;
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
@ -45,6 +71,7 @@ export const RecordTableCellFieldContextGeneric = ({
|
||||
}),
|
||||
displayedMaxRows: 1,
|
||||
isReadOnly: isFieldReadOnly,
|
||||
isForbidden: !hasObjectReadPermissions,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -16,11 +18,13 @@ type RecordTableCellFieldContextLabelIdentifierProps = {
|
||||
export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||
children,
|
||||
}: RecordTableCellFieldContextLabelIdentifierProps) => {
|
||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
||||
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
|
||||
useRecordIndexContextOrThrow();
|
||||
const { recordId, isReadOnly: isTableRowReadOnly } =
|
||||
useRecordTableRowContextOrThrow();
|
||||
|
||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isRecordTableScrolledLeftComponent = useRecoilComponentValueV2(
|
||||
@ -32,6 +36,13 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||
isRecordReadOnly: isTableRowReadOnly ?? false,
|
||||
});
|
||||
|
||||
const objectPermissions = getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
|
||||
const isLabelIdentifierCompact =
|
||||
@ -49,6 +60,7 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||
displayedMaxRows: 1,
|
||||
isReadOnly: isFieldReadOnly,
|
||||
maxWidth: columnDefinition.size,
|
||||
isForbidden: !hasObjectReadPermissions,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
@ -14,7 +15,6 @@ import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
@ -223,7 +223,11 @@ export const RecordTableHeaderCell = ({
|
||||
|
||||
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const isFirstRowActive = useRecoilComponentFamilyValueV2(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
@ -256,7 +260,7 @@ export const RecordTableHeaderCell = ({
|
||||
{(useIsMobile() || iconVisibility) &&
|
||||
!!column.isLabelIdentifier &&
|
||||
!isReadOnly &&
|
||||
!hasObjectReadOnlyPermission && (
|
||||
hasObjectUpdatePermissions && (
|
||||
<StyledHeaderIcon>
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -23,8 +23,6 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
||||
recordGroupDefinitionFamilyState(currentRecordGroupId),
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
@ -33,7 +31,13 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
||||
(field) => field.id === recordGroup?.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
if (!hasObjectUpdatePermissions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
city: 'Paris',
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -47,6 +48,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
createdBy: actorFieldValueForInput,
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -75,6 +77,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
} satisfies FieldActorForInputValue,
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -100,6 +103,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
companyId: '123',
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -133,6 +137,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
cache,
|
||||
record: companyRecord,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
const result = computeOptimisticRecordFromInput({
|
||||
@ -144,6 +149,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
__typename: 'test',
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
@ -178,6 +184,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
cache,
|
||||
record: companyRecord,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
const result = computeOptimisticRecordFromInput({
|
||||
@ -188,6 +195,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
companyId: '123',
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -208,6 +216,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
companyId: null,
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -232,6 +241,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
city: 'Paris',
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Should never occur, encountered unknown fields unknwon, foo, bar in objectMetadataItem person"`,
|
||||
@ -252,6 +262,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
company: {},
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`,
|
||||
@ -272,6 +283,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
||||
company: null,
|
||||
},
|
||||
cache,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`,
|
||||
|
||||
@ -22,13 +22,17 @@ type ComputeOptimisticCacheRecordInputArgs = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
recordInput: Partial<ObjectRecord>;
|
||||
currentWorkspaceMember: CurrentWorkspaceMember | null;
|
||||
} & Pick<GetRecordFromCacheArgs, 'cache' | 'objectMetadataItems'>;
|
||||
} & Pick<
|
||||
GetRecordFromCacheArgs,
|
||||
'cache' | 'objectMetadataItems' | 'objectPermissionsByObjectMetadataId'
|
||||
>;
|
||||
export const computeOptimisticRecordFromInput = ({
|
||||
objectMetadataItem,
|
||||
recordInput,
|
||||
cache,
|
||||
objectMetadataItems,
|
||||
currentWorkspaceMember,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: ComputeOptimisticCacheRecordInputArgs) => {
|
||||
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
||||
(recordKey) => {
|
||||
@ -177,6 +181,7 @@ export const computeOptimisticRecordFromInput = ({
|
||||
objectMetadataItem: targetObjectMetataDataItem,
|
||||
objectMetadataItems,
|
||||
recordId: recordInputFieldIdValue as string,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
optimisticRecord[relationFieldIdName] = recordInputFieldIdValue;
|
||||
|
||||
@ -4,6 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
|
||||
export type QueryCursorDirection = 'before' | 'after';
|
||||
|
||||
@ -13,12 +14,14 @@ export const generateFindManyRecordsQuery = ({
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
cursorDirection,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
computeReferences?: boolean;
|
||||
cursorDirection?: QueryCursorDirection;
|
||||
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||
}) => gql`
|
||||
query FindMany${capitalize(
|
||||
objectMetadataItem.namePlural,
|
||||
@ -38,6 +41,7 @@ query FindMany${capitalize(
|
||||
objectMetadataItem,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}
|
||||
cursor
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ export const SignInBackgroundMockContainer = () => {
|
||||
<StyledContainer>
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
recordIndexId,
|
||||
objectNamePlural,
|
||||
objectNameSingular,
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
type PageAddButtonProps = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
if (hasObjectReadOnlyPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
title={isMobile ? '' : t`New record`}
|
||||
onClick={onClick}
|
||||
ariaLabel={t`New record`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
|
||||
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||
import { DELETED_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/deletedWorkspaceMemberQueryFragment';
|
||||
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
||||
@ -5,6 +6,7 @@ import { gql } from '@apollo/client';
|
||||
|
||||
export const USER_QUERY_FRAGMENT = gql`
|
||||
${ROLE_FRAGMENT}
|
||||
${OBJECT_PERMISSION_FRAGMENT}
|
||||
fragment UserQueryFragment on User {
|
||||
id
|
||||
firstName
|
||||
@ -26,6 +28,9 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
currentUserWorkspace {
|
||||
settingsPermissions
|
||||
objectRecordsPermissions
|
||||
objectPermissions {
|
||||
...ObjectPermissionFragment
|
||||
}
|
||||
}
|
||||
currentWorkspace {
|
||||
id
|
||||
|
||||
@ -97,6 +97,7 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
indexIdentifierUrl: () => '',
|
||||
onIndexRecordsLoaded: () => {},
|
||||
objectNamePlural: CoreObjectNamePlural.Company,
|
||||
|
||||
@ -9,6 +9,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
@ -33,7 +34,7 @@ export const usePersistViewFieldRecords = () => {
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createViewFieldRecords = useCallback(
|
||||
@ -62,6 +63,7 @@ export const usePersistViewFieldRecords = () => {
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [record],
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
},
|
||||
}),
|
||||
@ -73,6 +75,7 @@ export const usePersistViewFieldRecords = () => {
|
||||
createOneRecordMutation,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
@ -37,7 +38,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createViewFilterGroupRecord = useCallback(
|
||||
@ -65,6 +66,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [record],
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -80,6 +82,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
||||
createOneRecordMutation,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
@ -37,7 +38,7 @@ export const usePersistViewFilterRecords = () => {
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createViewFilterRecords = useCallback(
|
||||
@ -70,6 +71,7 @@ export const usePersistViewFilterRecords = () => {
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [record],
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
},
|
||||
}),
|
||||
@ -81,6 +83,7 @@ export const usePersistViewFilterRecords = () => {
|
||||
createOneRecordMutation,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
@ -37,7 +38,7 @@ export const usePersistViewSortRecords = () => {
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createViewSortRecords = useCallback(
|
||||
@ -64,6 +65,7 @@ export const usePersistViewSortRecords = () => {
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [record],
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
},
|
||||
}),
|
||||
@ -75,6 +77,7 @@ export const usePersistViewSortRecords = () => {
|
||||
createOneRecordMutation,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObje
|
||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
@ -45,6 +46,8 @@ export const useViewFromQueryParams = () => {
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const queryParamsValidation = filterQueryParamsSchema.safeParse(
|
||||
qs.parse(searchParams.toString()),
|
||||
);
|
||||
@ -122,6 +125,7 @@ export const useViewFromQueryParams = () => {
|
||||
query: generateFindManyRecordsQuery({
|
||||
objectMetadataItem: relationObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}),
|
||||
variables: {
|
||||
filter: {
|
||||
@ -182,6 +186,7 @@ export const useViewFromQueryParams = () => {
|
||||
hasFiltersQueryParams,
|
||||
objectMetadataItem.fields,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { DELETE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/deleteWorkflowVersionStep';
|
||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||
import { useApolloClient, useMutation } from '@apollo/client';
|
||||
@ -17,6 +18,7 @@ import {
|
||||
export const useDeleteWorkflowVersionStep = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
||||
});
|
||||
@ -68,6 +70,7 @@ export const useDeleteWorkflowVersionStep = () => {
|
||||
cache: apolloClient.cache,
|
||||
record: newCachedRecord,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user