[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 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; }>;
|
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 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<{
|
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||||
workflowVersionId: Scalars['String'];
|
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`
|
export const SettingPermissionFragmentFragmentDoc = gql`
|
||||||
fragment SettingPermissionFragment on SettingPermission {
|
fragment SettingPermissionFragment on SettingPermission {
|
||||||
id
|
id
|
||||||
@ -3259,6 +3250,15 @@ export const DeletedWorkspaceMemberQueryFragmentFragmentDoc = gql`
|
|||||||
userEmail
|
userEmail
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const ObjectPermissionFragmentFragmentDoc = gql`
|
||||||
|
fragment ObjectPermissionFragment on ObjectPermission {
|
||||||
|
objectMetadataId
|
||||||
|
canReadObjectRecords
|
||||||
|
canUpdateObjectRecords
|
||||||
|
canSoftDeleteObjectRecords
|
||||||
|
canDestroyObjectRecords
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const RoleFragmentFragmentDoc = gql`
|
export const RoleFragmentFragmentDoc = gql`
|
||||||
fragment RoleFragment on Role {
|
fragment RoleFragment on Role {
|
||||||
id
|
id
|
||||||
@ -3295,6 +3295,9 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
currentUserWorkspace {
|
currentUserWorkspace {
|
||||||
settingsPermissions
|
settingsPermissions
|
||||||
objectRecordsPermissions
|
objectRecordsPermissions
|
||||||
|
objectPermissions {
|
||||||
|
...ObjectPermissionFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
currentWorkspace {
|
currentWorkspace {
|
||||||
id
|
id
|
||||||
@ -3364,6 +3367,7 @@ export const UserQueryFragmentFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
${WorkspaceMemberQueryFragmentFragmentDoc}
|
${WorkspaceMemberQueryFragmentFragmentDoc}
|
||||||
${DeletedWorkspaceMemberQueryFragmentFragmentDoc}
|
${DeletedWorkspaceMemberQueryFragmentFragmentDoc}
|
||||||
|
${ObjectPermissionFragmentFragmentDoc}
|
||||||
${RoleFragmentFragmentDoc}`;
|
${RoleFragmentFragmentDoc}`;
|
||||||
export const GetTimelineCalendarEventsFromCompanyIdDocument = gql`
|
export const GetTimelineCalendarEventsFromCompanyIdDocument = gql`
|
||||||
query GetTimelineCalendarEventsFromCompanyId($companyId: UUID!, $page: Int!, $pageSize: Int!) {
|
query GetTimelineCalendarEventsFromCompanyId($companyId: UUID!, $page: Int!, $pageSize: Int!) {
|
||||||
|
|||||||
@ -66,8 +66,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
position: 0,
|
position: 0,
|
||||||
isPinned: true,
|
isPinned: true,
|
||||||
Icon: IconPlus,
|
Icon: IconPlus,
|
||||||
shouldBeRegistered: ({ hasObjectReadOnlyPermission }) =>
|
shouldBeRegistered: ({ objectPermissions }) =>
|
||||||
!hasObjectReadOnlyPermission,
|
objectPermissions.canUpdateObjectRecords,
|
||||||
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
|
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
|
||||||
component: <CreateNewTableRecordNoSelectionRecordAction />,
|
component: <CreateNewTableRecordNoSelectionRecordAction />,
|
||||||
},
|
},
|
||||||
@ -194,10 +194,15 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
Icon: IconTrash,
|
Icon: IconTrash,
|
||||||
accent: 'default',
|
accent: 'default',
|
||||||
isPinned: true,
|
isPinned: true,
|
||||||
shouldBeRegistered: ({ selectedRecord, isSoftDeleteFilterActive }) =>
|
shouldBeRegistered: ({
|
||||||
|
selectedRecord,
|
||||||
|
isSoftDeleteFilterActive,
|
||||||
|
objectPermissions,
|
||||||
|
}) =>
|
||||||
isDefined(selectedRecord) &&
|
isDefined(selectedRecord) &&
|
||||||
!selectedRecord.isRemote &&
|
!selectedRecord.isRemote &&
|
||||||
!isSoftDeleteFilterActive,
|
!isSoftDeleteFilterActive &&
|
||||||
|
objectPermissions.canSoftDeleteObjectRecords,
|
||||||
availableOn: [
|
availableOn: [
|
||||||
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,
|
||||||
ActionViewType.SHOW_PAGE,
|
ActionViewType.SHOW_PAGE,
|
||||||
@ -215,12 +220,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
accent: 'default',
|
accent: 'default',
|
||||||
isPinned: true,
|
isPinned: true,
|
||||||
shouldBeRegistered: ({
|
shouldBeRegistered: ({
|
||||||
hasObjectReadOnlyPermission,
|
objectPermissions,
|
||||||
isRemote,
|
isRemote,
|
||||||
isSoftDeleteFilterActive,
|
isSoftDeleteFilterActive,
|
||||||
numberOfSelectedRecords,
|
numberOfSelectedRecords,
|
||||||
}) =>
|
}) =>
|
||||||
!hasObjectReadOnlyPermission &&
|
objectPermissions.canSoftDeleteObjectRecords &&
|
||||||
!isRemote &&
|
!isRemote &&
|
||||||
!isSoftDeleteFilterActive &&
|
!isSoftDeleteFilterActive &&
|
||||||
isDefined(numberOfSelectedRecords) &&
|
isDefined(numberOfSelectedRecords) &&
|
||||||
@ -268,12 +273,8 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
Icon: IconTrashX,
|
Icon: IconTrashX,
|
||||||
accent: 'danger',
|
accent: 'danger',
|
||||||
isPinned: true,
|
isPinned: true,
|
||||||
shouldBeRegistered: ({
|
shouldBeRegistered: ({ selectedRecord, objectPermissions, isRemote }) =>
|
||||||
selectedRecord,
|
objectPermissions.canDestroyObjectRecords &&
|
||||||
hasObjectReadOnlyPermission,
|
|
||||||
isRemote,
|
|
||||||
}) =>
|
|
||||||
!hasObjectReadOnlyPermission &&
|
|
||||||
!isRemote &&
|
!isRemote &&
|
||||||
isDefined(selectedRecord?.deletedAt),
|
isDefined(selectedRecord?.deletedAt),
|
||||||
availableOn: [
|
availableOn: [
|
||||||
@ -317,12 +318,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
accent: 'danger',
|
accent: 'danger',
|
||||||
isPinned: true,
|
isPinned: true,
|
||||||
shouldBeRegistered: ({
|
shouldBeRegistered: ({
|
||||||
hasObjectReadOnlyPermission,
|
objectPermissions,
|
||||||
isRemote,
|
isRemote,
|
||||||
isSoftDeleteFilterActive,
|
isSoftDeleteFilterActive,
|
||||||
numberOfSelectedRecords,
|
numberOfSelectedRecords,
|
||||||
}) =>
|
}) =>
|
||||||
!hasObjectReadOnlyPermission &&
|
objectPermissions.canDestroyObjectRecords &&
|
||||||
!isRemote &&
|
!isRemote &&
|
||||||
isDefined(isSoftDeleteFilterActive) &&
|
isDefined(isSoftDeleteFilterActive) &&
|
||||||
isSoftDeleteFilterActive &&
|
isSoftDeleteFilterActive &&
|
||||||
@ -343,14 +344,14 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
isPinned: true,
|
isPinned: true,
|
||||||
shouldBeRegistered: ({
|
shouldBeRegistered: ({
|
||||||
selectedRecord,
|
selectedRecord,
|
||||||
hasObjectReadOnlyPermission,
|
objectPermissions,
|
||||||
isRemote,
|
isRemote,
|
||||||
isShowPage,
|
isShowPage,
|
||||||
isSoftDeleteFilterActive,
|
isSoftDeleteFilterActive,
|
||||||
}) =>
|
}) =>
|
||||||
!isRemote &&
|
!isRemote &&
|
||||||
isDefined(selectedRecord?.deletedAt) &&
|
isDefined(selectedRecord?.deletedAt) &&
|
||||||
!hasObjectReadOnlyPermission &&
|
objectPermissions.canSoftDeleteObjectRecords &&
|
||||||
((isDefined(isShowPage) && isShowPage) ||
|
((isDefined(isShowPage) && isShowPage) ||
|
||||||
(isDefined(isSoftDeleteFilterActive) && isSoftDeleteFilterActive)),
|
(isDefined(isSoftDeleteFilterActive) && isSoftDeleteFilterActive)),
|
||||||
availableOn: [
|
availableOn: [
|
||||||
@ -370,12 +371,12 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
|
|||||||
accent: 'default',
|
accent: 'default',
|
||||||
isPinned: true,
|
isPinned: true,
|
||||||
shouldBeRegistered: ({
|
shouldBeRegistered: ({
|
||||||
hasObjectReadOnlyPermission,
|
objectPermissions,
|
||||||
isRemote,
|
isRemote,
|
||||||
isSoftDeleteFilterActive,
|
isSoftDeleteFilterActive,
|
||||||
numberOfSelectedRecords,
|
numberOfSelectedRecords,
|
||||||
}) =>
|
}) =>
|
||||||
!hasObjectReadOnlyPermission &&
|
objectPermissions.canSoftDeleteObjectRecords &&
|
||||||
!isRemote &&
|
!isRemote &&
|
||||||
isDefined(isSoftDeleteFilterActive) &&
|
isDefined(isSoftDeleteFilterActive) &&
|
||||||
isSoftDeleteFilterActive &&
|
isSoftDeleteFilterActive &&
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
|
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
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 { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
||||||
|
|
||||||
export type ShouldBeRegisteredFunctionParams = {
|
export type ShouldBeRegisteredFunctionParams = {
|
||||||
objectMetadataItem?: ObjectMetadataItem;
|
objectMetadataItem?: ObjectMetadataItem;
|
||||||
hasObjectReadOnlyPermission?: boolean;
|
objectPermissions: ObjectPermissions;
|
||||||
isWorkflowEnabled: boolean;
|
isWorkflowEnabled: boolean;
|
||||||
recordFilters?: RecordFilter[];
|
recordFilters?: RecordFilter[];
|
||||||
isShowPage?: boolean;
|
isShowPage?: boolean;
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType
|
|||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
@ -42,7 +42,9 @@ export const useShouldActionBeRegisteredParams = ({
|
|||||||
const selectedRecord =
|
const selectedRecord =
|
||||||
useRecoilValue(recordStoreFamilyState(recordId ?? '')) || undefined;
|
useRecoilValue(recordStoreFamilyState(recordId ?? '')) || undefined;
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem?.id,
|
||||||
|
);
|
||||||
|
|
||||||
const isNoteOrTask =
|
const isNoteOrTask =
|
||||||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note ||
|
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Note ||
|
||||||
@ -78,7 +80,7 @@ export const useShouldActionBeRegisteredParams = ({
|
|||||||
return {
|
return {
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
hasObjectReadOnlyPermission,
|
objectPermissions,
|
||||||
isNoteOrTask,
|
isNoteOrTask,
|
||||||
isInRightDrawer,
|
isInRightDrawer,
|
||||||
isSoftDeleteFilterActive,
|
isSoftDeleteFilterActive,
|
||||||
|
|||||||
@ -67,7 +67,10 @@ export const ActivityRichTextEditor = ({
|
|||||||
objectNameSingular: activityObjectNameSingular,
|
objectNameSingular: activityObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({ recordId: activityId });
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId: activityId,
|
||||||
|
objectMetadataId: objectMetadataItemActivity.id,
|
||||||
|
});
|
||||||
|
|
||||||
const isReadOnly = isFieldValueReadOnly({
|
const isReadOnly = isFieldValueReadOnly({
|
||||||
objectNameSingular: activityObjectNameSingular,
|
objectNameSingular: activityObjectNameSingular,
|
||||||
|
|||||||
@ -7,8 +7,11 @@ import { DropZone } from '@/activities/files/components/DropZone';
|
|||||||
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
import { useAttachments } from '@/activities/files/hooks/useAttachments';
|
||||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
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 { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import {
|
import {
|
||||||
AnimatedPlaceholder,
|
AnimatedPlaceholder,
|
||||||
AnimatedPlaceholderEmptyContainer,
|
AnimatedPlaceholderEmptyContainer,
|
||||||
@ -17,8 +20,6 @@ import {
|
|||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from 'twenty-ui/layout';
|
} from 'twenty-ui/layout';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
|
||||||
|
|
||||||
const StyledAttachmentsContainer = styled.div`
|
const StyledAttachmentsContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -47,8 +48,6 @@ export const Attachments = ({
|
|||||||
|
|
||||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]);
|
if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]);
|
||||||
};
|
};
|
||||||
@ -63,6 +62,16 @@ export const Attachments = ({
|
|||||||
|
|
||||||
const isAttachmentsEmpty = !attachments || attachments.length === 0;
|
const isAttachmentsEmpty = !attachments || attachments.length === 0;
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
if (loading && isAttachmentsEmpty) {
|
if (loading && isAttachmentsEmpty) {
|
||||||
return <SkeletonLoader />;
|
return <SkeletonLoader />;
|
||||||
}
|
}
|
||||||
@ -94,7 +103,7 @@ export const Attachments = ({
|
|||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
type="file"
|
type="file"
|
||||||
/>
|
/>
|
||||||
{!hasObjectReadOnlyPermission && (
|
{!hasObjectUpdatePermissions && (
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
title="Add file"
|
title="Add file"
|
||||||
@ -120,7 +129,7 @@ export const Attachments = ({
|
|||||||
title="All"
|
title="All"
|
||||||
attachments={attachments ?? []}
|
attachments={attachments ?? []}
|
||||||
button={
|
button={
|
||||||
!hasObjectReadOnlyPermission && (
|
!hasObjectUpdatePermissions && (
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@ -12,9 +12,10 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
|
||||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||||
|
|
||||||
export const usePrepareFindManyActivitiesQuery = ({
|
export const usePrepareFindManyActivitiesQuery = ({
|
||||||
activityObjectNameSingular,
|
activityObjectNameSingular,
|
||||||
@ -32,6 +33,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
|||||||
|
|
||||||
const cache = useApolloClient().cache;
|
const cache = useApolloClient().cache;
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const { upsertFindManyRecordsQueryInCache: upsertFindManyActivitiesInCache } =
|
const { upsertFindManyRecordsQueryInCache: upsertFindManyActivitiesInCache } =
|
||||||
useUpsertFindManyRecordsQueryInCache({
|
useUpsertFindManyRecordsQueryInCache({
|
||||||
@ -64,6 +66,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
|||||||
objectMetadataItem: targetableObjectMetadataItem,
|
objectMetadataItem: targetableObjectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const activityTargets: (TaskTarget | NoteTarget)[] =
|
const activityTargets: (TaskTarget | NoteTarget)[] =
|
||||||
|
|||||||
@ -3,9 +3,12 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
|||||||
import { NoteList } from '@/activities/notes/components/NoteList';
|
import { NoteList } from '@/activities/notes/components/NoteList';
|
||||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
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 styled from '@emotion/styled';
|
||||||
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import {
|
import {
|
||||||
AnimatedPlaceholder,
|
AnimatedPlaceholder,
|
||||||
AnimatedPlaceholderEmptyContainer,
|
AnimatedPlaceholderEmptyContainer,
|
||||||
@ -14,8 +17,6 @@ import {
|
|||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from 'twenty-ui/layout';
|
} from 'twenty-ui/layout';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
|
||||||
|
|
||||||
const StyledNotesContainer = styled.div`
|
const StyledNotesContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -32,14 +33,22 @@ export const Notes = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { notes, loading } = useNotes(targetableObject);
|
const { notes, loading } = useNotes(targetableObject);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isNotesEmpty = !notes || notes.length === 0;
|
const isNotesEmpty = !notes || notes.length === 0;
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular: targetableObject.targetObjectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
if (loading && isNotesEmpty) {
|
if (loading && isNotesEmpty) {
|
||||||
return <SkeletonLoader />;
|
return <SkeletonLoader />;
|
||||||
}
|
}
|
||||||
@ -59,7 +68,7 @@ export const Notes = ({
|
|||||||
There are no associated notes with this record.
|
There are no associated notes with this record.
|
||||||
</AnimatedPlaceholderEmptySubTitle>
|
</AnimatedPlaceholderEmptySubTitle>
|
||||||
</AnimatedPlaceholderEmptyTextContainer>
|
</AnimatedPlaceholderEmptyTextContainer>
|
||||||
{!hasObjectReadOnlyPermission && (
|
{!hasObjectUpdatePermissions && (
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
title="New note"
|
title="New note"
|
||||||
@ -81,7 +90,7 @@ export const Notes = ({
|
|||||||
title="All"
|
title="All"
|
||||||
notes={notes}
|
notes={notes}
|
||||||
button={
|
button={
|
||||||
!hasObjectReadOnlyPermission && (
|
!hasObjectUpdatePermissions && (
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@ -1,27 +1,31 @@
|
|||||||
import { isNonEmptyArray } from '@sniptt/guards';
|
|
||||||
|
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
|
|
||||||
export const AddTaskButton = ({
|
export const AddTaskButton = ({
|
||||||
activityTargetableObjects,
|
activityTargetableObject,
|
||||||
}: {
|
}: {
|
||||||
activityTargetableObjects?: ActivityTargetableObject[];
|
activityTargetableObject: ActivityTargetableObject;
|
||||||
}) => {
|
}) => {
|
||||||
const openCreateActivity = useOpenCreateActivityDrawer({
|
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular: activityTargetableObject.targetObjectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
!isNonEmptyArray(activityTargetableObjects) ||
|
objectMetadataItem.id,
|
||||||
hasObjectReadOnlyPermission
|
);
|
||||||
) {
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
|
if (!hasObjectUpdatePermissions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +37,7 @@ export const AddTaskButton = ({
|
|||||||
title="Add task"
|
title="Add task"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openCreateActivity({
|
openCreateActivity({
|
||||||
targetableObjects: activityTargetableObjects,
|
targetableObjects: [activityTargetableObject],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export const ObjectTasks = ({ targetableObject }: ObjectTasksProps) => {
|
|||||||
<ObjectFilterDropdownComponentInstanceContext.Provider
|
<ObjectFilterDropdownComponentInstanceContext.Provider
|
||||||
value={{ instanceId: 'entity-tasks-filter-scope' }}
|
value={{ instanceId: 'entity-tasks-filter-scope' }}
|
||||||
>
|
>
|
||||||
<TaskGroups targetableObjects={[targetableObject]} />
|
<TaskGroups targetableObject={targetableObject} />
|
||||||
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
</ObjectFilterDropdownComponentInstanceContext.Provider>
|
||||||
</StyledContainer>
|
</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 { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { Task } from '@/activities/types/Task';
|
import { Task } from '@/activities/types/Task';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
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 { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import groupBy from 'lodash.groupby';
|
import groupBy from 'lodash.groupby';
|
||||||
import { AddTaskButton } from './AddTaskButton';
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
import { TaskList } from './TaskList';
|
import { Button } from 'twenty-ui/input';
|
||||||
import {
|
import {
|
||||||
AnimatedPlaceholder,
|
AnimatedPlaceholder,
|
||||||
AnimatedPlaceholderEmptyContainer,
|
AnimatedPlaceholderEmptyContainer,
|
||||||
@ -20,8 +21,8 @@ import {
|
|||||||
AnimatedPlaceholderEmptyTitle,
|
AnimatedPlaceholderEmptyTitle,
|
||||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||||
} from 'twenty-ui/layout';
|
} from 'twenty-ui/layout';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { AddTaskButton } from './AddTaskButton';
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
import { TaskList } from './TaskList';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -31,15 +32,23 @@ const StyledContainer = styled.div`
|
|||||||
|
|
||||||
type TaskGroupsProps = {
|
type TaskGroupsProps = {
|
||||||
filterDropdownId?: string;
|
filterDropdownId?: string;
|
||||||
targetableObjects?: ActivityTargetableObject[];
|
targetableObject: ActivityTargetableObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
export const TaskGroups = ({ targetableObject }: TaskGroupsProps) => {
|
||||||
const { tasks, tasksLoading } = useTasks({
|
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({
|
const openCreateActivity = useOpenCreateActivityDrawer({
|
||||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||||
@ -74,14 +83,14 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
|||||||
All tasks addressed. Maintain the momentum.
|
All tasks addressed. Maintain the momentum.
|
||||||
</AnimatedPlaceholderEmptySubTitle>
|
</AnimatedPlaceholderEmptySubTitle>
|
||||||
</AnimatedPlaceholderEmptyTextContainer>
|
</AnimatedPlaceholderEmptyTextContainer>
|
||||||
{!hasObjectReadOnlyPermission && (
|
{!hasObjectUpdatePermissions && (
|
||||||
<Button
|
<Button
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
title="New task"
|
title="New task"
|
||||||
variant={'secondary'}
|
variant={'secondary'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openCreateActivity({
|
openCreateActivity({
|
||||||
targetableObjects: targetableObjects ?? [],
|
targetableObjects: [targetableObject],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -107,7 +116,7 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
|||||||
tasks={tasksByStatus}
|
tasks={tasksByStatus}
|
||||||
button={
|
button={
|
||||||
(status === 'TODO' || !hasTodoStatus) && (
|
(status === 'TODO' || !hasTodoStatus) && (
|
||||||
<AddTaskButton activityTargetableObjects={targetableObjects} />
|
<AddTaskButton activityTargetableObject={targetableObject} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -42,12 +42,10 @@ export const Empty: Story = {};
|
|||||||
|
|
||||||
export const WithTasks: Story = {
|
export const WithTasks: Story = {
|
||||||
args: {
|
args: {
|
||||||
targetableObjects: [
|
targetableObject: {
|
||||||
{
|
id: mockedTasks[0].taskTargets?.[0].personId,
|
||||||
id: mockedTasks[0].taskTargets?.[0].personId,
|
targetObjectNameSingular: 'person',
|
||||||
targetObjectNameSingular: 'person',
|
} as ActivityTargetableObject,
|
||||||
},
|
|
||||||
] as ActivityTargetableObject[],
|
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
|
|||||||
@ -13,8 +13,9 @@ import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRec
|
|||||||
import { encodeCursor } from '@/apollo/utils/encodeCursor';
|
import { encodeCursor } from '@/apollo/utils/encodeCursor';
|
||||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||||
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
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.
|
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[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
shouldMatchRootQueryFilter?: boolean;
|
shouldMatchRootQueryFilter?: boolean;
|
||||||
checkForRecordInCache?: boolean;
|
checkForRecordInCache?: boolean;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
};
|
};
|
||||||
export const triggerCreateRecordsOptimisticEffect = ({
|
export const triggerCreateRecordsOptimisticEffect = ({
|
||||||
cache,
|
cache,
|
||||||
@ -36,6 +38,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
shouldMatchRootQueryFilter,
|
shouldMatchRootQueryFilter,
|
||||||
checkForRecordInCache = false,
|
checkForRecordInCache = false,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: TriggerCreateRecordsOptimisticEffectArgs) => {
|
}: TriggerCreateRecordsOptimisticEffectArgs) => {
|
||||||
const getRecordNodeFromCache = (recordId: string): RecordGqlNode | null => {
|
const getRecordNodeFromCache = (recordId: string): RecordGqlNode | null => {
|
||||||
const cachedRecord = getRecordFromCache({
|
const cachedRecord = getRecordFromCache({
|
||||||
@ -43,6 +46,7 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordId,
|
recordId,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
return getRecordNodeFromRecord({
|
return getRecordNodeFromRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { UserWorkspace } from '~/generated/graphql';
|
|
||||||
import { createState } from 'twenty-ui/utilities';
|
import { createState } from 'twenty-ui/utilities';
|
||||||
|
import { UserWorkspace } from '~/generated/graphql';
|
||||||
|
|
||||||
export type CurrentUserWorkspace = Pick<
|
export type CurrentUserWorkspace = Pick<
|
||||||
UserWorkspace,
|
UserWorkspace,
|
||||||
'settingsPermissions' | 'objectRecordsPermissions'
|
'settingsPermissions' | 'objectRecordsPermissions' | 'objectPermissions'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const currentUserWorkspaceState =
|
export const currentUserWorkspaceState =
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { commandMenuNavigationRecordsState } from '@/command-menu/states/command
|
|||||||
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
@ -18,6 +19,7 @@ export const CommandMenuContextChipRecordSetterEffect = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const commandMenuNavigationStack = useRecoilValue(
|
const commandMenuNavigationStack = useRecoilValue(
|
||||||
commandMenuNavigationStackState,
|
commandMenuNavigationStackState,
|
||||||
@ -46,6 +48,7 @@ export const CommandMenuContextChipRecordSetterEffect = () => {
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!record) {
|
if (!record) {
|
||||||
@ -68,6 +71,7 @@ export const CommandMenuContextChipRecordSetterEffect = () => {
|
|||||||
commandMenuNavigationStack.length,
|
commandMenuNavigationStack.length,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
setCommandMenuNavigationRecords,
|
setCommandMenuNavigationRecords,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -50,6 +50,7 @@ export const initialFavorites: Favorite[] = [
|
|||||||
key: mockId,
|
key: mockId,
|
||||||
labelIdentifier: 'favoriteLabel',
|
labelIdentifier: 'favoriteLabel',
|
||||||
avatarUrl: 'example.com',
|
avatarUrl: 'example.com',
|
||||||
|
company: { id: '5', name: 'Company Test 2' },
|
||||||
avatarType: 'squared' as AvatarType,
|
avatarType: 'squared' as AvatarType,
|
||||||
link: 'example.com',
|
link: 'example.com',
|
||||||
recordId: '1',
|
recordId: '1',
|
||||||
@ -86,17 +87,17 @@ export const sortedFavorites = [
|
|||||||
__typename: 'Favorite',
|
__typename: 'Favorite',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
__typename: 'Favorite',
|
||||||
position: 2,
|
|
||||||
key: '8f3b2121-f194-4ba4-9fbf-2d5a37126806',
|
|
||||||
labelIdentifier: 'favoriteLabel',
|
|
||||||
avatarUrl: 'example.com',
|
|
||||||
link: 'example.com',
|
|
||||||
recordId: '1',
|
|
||||||
avatarType: 'squared',
|
avatarType: 'squared',
|
||||||
|
avatarUrl: undefined,
|
||||||
favoriteFolderId: '1',
|
favoriteFolderId: '1',
|
||||||
forWorkspaceMemberId: '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;
|
} as ProcessedFavorite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return null;
|
||||||
...favorite,
|
|
||||||
} as ProcessedFavorite;
|
|
||||||
})
|
})
|
||||||
|
.filter(isDefined)
|
||||||
.sort((a, b) => a.position - b.position);
|
.sort((a, b) => a.position - b.position);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/components/NavigationDrawerItemForObjectMetadataItem';
|
import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/components/NavigationDrawerItemForObjectMetadataItem';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
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 { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||||
@ -27,6 +29,8 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
|||||||
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
|
useNavigationSection('Objects' + (isRemote ? 'Remote' : 'Workspace'));
|
||||||
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
const isNavigationSectionOpen = useRecoilValue(isNavigationSectionOpenState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const sortedStandardObjectMetadataItems = [...objectMetadataItems]
|
const sortedStandardObjectMetadataItems = [...objectMetadataItems]
|
||||||
.filter((item) => ORDERED_STANDARD_OBJECTS.includes(item.nameSingular))
|
.filter((item) => ORDERED_STANDARD_OBJECTS.includes(item.nameSingular))
|
||||||
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
.sort((objectMetadataItemA, objectMetadataItemB) => {
|
||||||
@ -58,6 +62,15 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
|||||||
...sortedCustomObjectMetadataItems,
|
...sortedCustomObjectMetadataItems,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const objectMetadataItemsForNavigationItemsWithReadPermission =
|
||||||
|
objectMetadataItemsForNavigationItems.filter(
|
||||||
|
(objectMetadataItem) =>
|
||||||
|
getObjectPermissionsForObject(
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
|
objectMetadataItem.id,
|
||||||
|
).canReadObjectRecords,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
objectMetadataItems.length > 0 && (
|
objectMetadataItems.length > 0 && (
|
||||||
<NavigationDrawerSection>
|
<NavigationDrawerSection>
|
||||||
@ -68,12 +81,14 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({
|
|||||||
/>
|
/>
|
||||||
</NavigationDrawerAnimatedCollapseWrapper>
|
</NavigationDrawerAnimatedCollapseWrapper>
|
||||||
{isNavigationSectionOpen &&
|
{isNavigationSectionOpen &&
|
||||||
objectMetadataItemsForNavigationItems.map((objectMetadataItem) => (
|
objectMetadataItemsForNavigationItemsWithReadPermission.map(
|
||||||
<NavigationDrawerItemForObjectMetadataItem
|
(objectMetadataItem) => (
|
||||||
key={`navigation-drawer-item-${objectMetadataItem.id}`}
|
<NavigationDrawerItemForObjectMetadataItem
|
||||||
objectMetadataItem={objectMetadataItem}
|
key={`navigation-drawer-item-${objectMetadataItem.id}`}
|
||||||
/>
|
objectMetadataItem={objectMetadataItem}
|
||||||
))}
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</NavigationDrawerSection>
|
</NavigationDrawerSection>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
|||||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'id',
|
(field) => field.name === 'id',
|
||||||
)!,
|
)!,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
expect(normalizeGQLField(res)).toEqual(normalizeGQLField('id'));
|
expect(normalizeGQLField(res)).toEqual(normalizeGQLField('id'));
|
||||||
});
|
});
|
||||||
@ -28,6 +29,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
|||||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'name',
|
(field) => field.name === 'name',
|
||||||
)!,
|
)!,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
expect(normalizeGQLField(res)).toEqual(
|
expect(normalizeGQLField(res)).toEqual(
|
||||||
normalizeGQLField(`name
|
normalizeGQLField(`name
|
||||||
@ -45,6 +47,7 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
|||||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'company',
|
(field) => field.name === 'company',
|
||||||
)!,
|
)!,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
expect(normalizeGQLField(res)).toEqual(
|
expect(normalizeGQLField(res)).toEqual(
|
||||||
normalizeGQLField(`company
|
normalizeGQLField(`company
|
||||||
@ -122,6 +125,7 @@ idealCustomerProfile
|
|||||||
fieldMetadata: personObjectMetadataItem.fields.find(
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'company',
|
(field) => field.name === 'company',
|
||||||
)!,
|
)!,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
expect(normalizeGQLField(res)).toEqual(
|
expect(normalizeGQLField(res)).toEqual(
|
||||||
normalizeGQLField(`company
|
normalizeGQLField(`company
|
||||||
|
|||||||
@ -30,6 +30,15 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
|
|||||||
avatarUrl: true,
|
avatarUrl: true,
|
||||||
companyId: true,
|
companyId: true,
|
||||||
},
|
},
|
||||||
|
objectPermissionsByObjectMetadataId: {
|
||||||
|
[personObjectMetadataItem.id]: {
|
||||||
|
canReadObjectRecords: true,
|
||||||
|
canUpdateObjectRecords: true,
|
||||||
|
canSoftDeleteObjectRecords: true,
|
||||||
|
canDestroyObjectRecords: true,
|
||||||
|
objectMetadataId: personObjectMetadataItem.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(normalizeGQLQuery(res)).toEqual(
|
expect(normalizeGQLQuery(res)).toEqual(
|
||||||
normalizeGQLQuery(`{
|
normalizeGQLQuery(`{
|
||||||
@ -124,6 +133,15 @@ describe('mapObjectMetadataToGraphQLQuery', () => {
|
|||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
objectMetadataItem: personObjectMetadataItem,
|
objectMetadataItem: personObjectMetadataItem,
|
||||||
recordGqlFields: { company: { id: true }, id: true, name: true },
|
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(
|
expect(normalizeGQLQuery(res)).toEqual(
|
||||||
normalizeGQLQuery(`{
|
normalizeGQLQuery(`{
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export const formatFieldMetadataItemAsFieldDefinition = ({
|
|||||||
relationObjectMetadataItem?.nameSingular ?? '',
|
relationObjectMetadataItem?.nameSingular ?? '',
|
||||||
relationObjectMetadataNamePlural:
|
relationObjectMetadataNamePlural:
|
||||||
relationObjectMetadataItem?.namePlural ?? '',
|
relationObjectMetadataItem?.namePlural ?? '',
|
||||||
|
relationObjectMetadataId: relationObjectMetadataItem?.id ?? '',
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular ?? '',
|
objectMetadataNameSingular: objectMetadataItem.nameSingular ?? '',
|
||||||
targetFieldMetadataName:
|
targetFieldMetadataName:
|
||||||
field.relationDefinition?.targetFieldMetadata?.name ?? '',
|
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 { isUndefined } from '@sniptt/guards';
|
||||||
import {
|
import {
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
|
ObjectPermission,
|
||||||
RelationDefinitionType,
|
RelationDefinitionType,
|
||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||||
import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField';
|
import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||||
|
|
||||||
type MapFieldMetadataToGraphQLQueryArgs = {
|
type MapFieldMetadataToGraphQLQueryArgs = {
|
||||||
@ -19,6 +22,7 @@ type MapFieldMetadataToGraphQLQueryArgs = {
|
|||||||
>;
|
>;
|
||||||
relationRecordGqlFields?: RecordGqlFields;
|
relationRecordGqlFields?: RecordGqlFields;
|
||||||
computeReferences?: boolean;
|
computeReferences?: boolean;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
};
|
};
|
||||||
// TODO: change ObjectMetadataItems mock before refactoring with relationDefinition computed field
|
// TODO: change ObjectMetadataItems mock before refactoring with relationDefinition computed field
|
||||||
export const mapFieldMetadataToGraphQLQuery = ({
|
export const mapFieldMetadataToGraphQLQuery = ({
|
||||||
@ -27,11 +31,17 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
|||||||
fieldMetadata,
|
fieldMetadata,
|
||||||
relationRecordGqlFields,
|
relationRecordGqlFields,
|
||||||
computeReferences = false,
|
computeReferences = false,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: MapFieldMetadataToGraphQLQueryArgs): string => {
|
}: MapFieldMetadataToGraphQLQueryArgs): string => {
|
||||||
const fieldType = fieldMetadata.type;
|
const fieldType = fieldMetadata.type;
|
||||||
|
|
||||||
const fieldIsNonCompositeField = isNonCompositeField(fieldType);
|
const fieldIsNonCompositeField = isNonCompositeField(fieldType);
|
||||||
|
|
||||||
|
const objectPermission = getObjectPermissionsForObject(
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
|
fieldMetadata.relationDefinition?.targetObjectMetadata.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (fieldIsNonCompositeField) {
|
if (fieldIsNonCompositeField) {
|
||||||
return gqlField;
|
return gqlField;
|
||||||
}
|
}
|
||||||
@ -51,6 +61,15 @@ export const mapFieldMetadataToGraphQLQuery = ({
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isDefined(objectPermissionsByObjectMetadataId) &&
|
||||||
|
isDefined(relationMetadataItem.id)
|
||||||
|
) {
|
||||||
|
if (!objectPermission.canReadObjectRecords) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (gqlField === fieldMetadata.settings?.joinColumnName) {
|
if (gqlField === fieldMetadata.settings?.joinColumnName) {
|
||||||
return `${gqlField}`;
|
return `${gqlField}`;
|
||||||
}
|
}
|
||||||
@ -62,6 +81,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
recordGqlFields: relationRecordGqlFields,
|
recordGqlFields: relationRecordGqlFields,
|
||||||
computeReferences: computeReferences,
|
computeReferences: computeReferences,
|
||||||
isRootLevel: false,
|
isRootLevel: false,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}`;
|
})}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +100,15 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isDefined(objectPermissionsByObjectMetadataId) &&
|
||||||
|
isDefined(relationMetadataItem.id)
|
||||||
|
) {
|
||||||
|
if (!objectPermission.canReadObjectRecords) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return `${gqlField}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
edges {
|
edges {
|
||||||
@ -89,6 +118,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
recordGqlFields: relationRecordGqlFields,
|
recordGqlFields: relationRecordGqlFields,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
isRootLevel: false,
|
isRootLevel: false,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
@ -1,25 +1,47 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||||
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery';
|
||||||
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
|
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
|
||||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||||
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
|
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type MapObjectMetadataToGraphQLQueryArgs = {
|
type MapObjectMetadataToGraphQLQueryArgs = {
|
||||||
objectMetadataItems: ObjectMetadataItem[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
objectMetadataItem: Pick<ObjectMetadataItem, 'nameSingular' | 'fields'>;
|
objectMetadataItem: Pick<
|
||||||
|
ObjectMetadataItem,
|
||||||
|
'nameSingular' | 'fields' | 'id'
|
||||||
|
>;
|
||||||
recordGqlFields?: RecordGqlFields;
|
recordGqlFields?: RecordGqlFields;
|
||||||
computeReferences?: boolean;
|
computeReferences?: boolean;
|
||||||
isRootLevel?: boolean;
|
isRootLevel?: boolean;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mapObjectMetadataToGraphQLQuery = ({
|
export const mapObjectMetadataToGraphQLQuery = ({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
computeReferences = false,
|
computeReferences = false,
|
||||||
isRootLevel = true,
|
isRootLevel = true,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: MapObjectMetadataToGraphQLQueryArgs): string => {
|
}: MapObjectMetadataToGraphQLQueryArgs): string => {
|
||||||
|
if (
|
||||||
|
!isRootLevel &&
|
||||||
|
isDefined(objectPermissionsByObjectMetadataId) &&
|
||||||
|
isDefined(objectMetadataItem.id)
|
||||||
|
) {
|
||||||
|
const objectPermission = getObjectPermissionsForObject(
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
if (!objectPermission.canReadObjectRecords) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const manyToOneRelationFields = objectMetadataItem?.fields
|
const manyToOneRelationFields = objectMetadataItem?.fields
|
||||||
.filter((field) => field.isActive)
|
.filter((field) => field.isActive)
|
||||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
.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 `{
|
return `{
|
||||||
__typename
|
__typename
|
||||||
${gqlFieldWithFieldMetadataThatSouldBeQueried
|
${mappedFields}
|
||||||
.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')}
|
|
||||||
}`;
|
}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { prefillRecord } from '@/object-record/utils/prefillRecord';
|
import { prefillRecord } from '@/object-record/utils/prefillRecord';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
@ -21,6 +22,8 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
|
|||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
});
|
});
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
return (record: ObjectRecord) => {
|
return (record: ObjectRecord) => {
|
||||||
@ -42,6 +45,7 @@ export const useCreateOneRecordInCache = <T extends ObjectRecord>({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
computeReferences: true,
|
computeReferences: true,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
|||||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
export const useGetRecordFromCache = ({
|
export const useGetRecordFromCache = ({
|
||||||
@ -24,6 +25,7 @@ export const useGetRecordFromCache = ({
|
|||||||
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
recordGqlFields ?? generateDepthOneRecordGqlFields({ objectMetadataItem });
|
||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ export const useGetRecordFromCache = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields: appliedRecordGqlFields,
|
recordGqlFields: appliedRecordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -45,6 +48,7 @@ export const useGetRecordFromCache = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
appliedRecordGqlFields,
|
appliedRecordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|||||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -18,6 +19,8 @@ export const useReadFindManyRecordsQueryInCache = ({
|
|||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const readFindManyRecordsQueryInCache = <
|
const readFindManyRecordsQueryInCache = <
|
||||||
T extends ObjectRecord = ObjectRecord,
|
T extends ObjectRecord = ObjectRecord,
|
||||||
>({
|
>({
|
||||||
@ -31,6 +34,7 @@ export const useReadFindManyRecordsQueryInCache = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const existingRecordsQueryResult =
|
const existingRecordsQueryResult =
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ export const useUpsertFindManyRecordsQueryInCache = ({
|
|||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const upsertFindManyRecordsQueryInCache = <
|
const upsertFindManyRecordsQueryInCache = <
|
||||||
T extends ObjectRecord = ObjectRecord,
|
T extends ObjectRecord = ObjectRecord,
|
||||||
@ -35,6 +37,7 @@ export const useUpsertFindManyRecordsQueryInCache = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newObjectRecordConnection = getRecordConnectionFromRecords({
|
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 { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
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 { isEmptyObject } from '~/utils/isEmptyObject';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export type GetRecordFromCacheArgs = {
|
export type GetRecordFromCacheArgs = {
|
||||||
cache: ApolloCache<object>;
|
cache: ApolloCache<object>;
|
||||||
@ -16,6 +17,7 @@ export type GetRecordFromCacheArgs = {
|
|||||||
objectMetadataItems: ObjectMetadataItem[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
recordGqlFields?: RecordGqlFields;
|
recordGqlFields?: RecordGqlFields;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
};
|
};
|
||||||
export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
@ -23,6 +25,7 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
cache,
|
cache,
|
||||||
recordId,
|
recordId,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: GetRecordFromCacheArgs) => {
|
}: GetRecordFromCacheArgs) => {
|
||||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||||
return null;
|
return null;
|
||||||
@ -39,6 +42,7 @@ export const getRecordFromCache = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields: appliedRecordGqlFields,
|
recordGqlFields: appliedRecordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
|
|||||||
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
export const updateRecordFromCache = <T extends ObjectRecord>({
|
export const updateRecordFromCache = <T extends ObjectRecord>({
|
||||||
@ -15,12 +16,14 @@ export const updateRecordFromCache = <T extends ObjectRecord>({
|
|||||||
cache,
|
cache,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
record,
|
record,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataItems: ObjectMetadataItem[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
cache: ApolloCache<object>;
|
cache: ApolloCache<object>;
|
||||||
recordGqlFields: Record<string, boolean>;
|
recordGqlFields: Record<string, boolean>;
|
||||||
record: T;
|
record: T;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
}) => {
|
}) => {
|
||||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||||
return null;
|
return null;
|
||||||
@ -35,6 +38,7 @@ export const updateRecordFromCache = <T extends ObjectRecord>({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
computeReferences: true,
|
computeReferences: true,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateReco
|
|||||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
|
|
||||||
// Mocks
|
// Mocks
|
||||||
jest.mock('@apollo/client');
|
jest.mock('@apollo/client');
|
||||||
@ -25,6 +26,10 @@ const mockGqlFieldToFieldMap = {
|
|||||||
totalCount: ['name', AggregateOperations.COUNT],
|
totalCount: ['name', AggregateOperations.COUNT],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||||
|
apolloMocks: [],
|
||||||
|
});
|
||||||
|
|
||||||
describe('useAggregateRecords', () => {
|
describe('useAggregateRecords', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(useObjectMetadataItem as jest.Mock).mockReturnValue({
|
(useObjectMetadataItem as jest.Mock).mockReturnValue({
|
||||||
@ -44,14 +49,18 @@ describe('useAggregateRecords', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should format data correctly', () => {
|
it('should format data correctly', () => {
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(
|
||||||
useAggregateRecords({
|
() =>
|
||||||
objectNameSingular: 'opportunity',
|
useAggregateRecords({
|
||||||
recordGqlFieldsAggregate: {
|
objectNameSingular: 'opportunity',
|
||||||
amount: [AggregateOperations.SUM, AggregateOperations.AVG],
|
recordGqlFieldsAggregate: {
|
||||||
name: [AggregateOperations.COUNT],
|
amount: [AggregateOperations.SUM, AggregateOperations.AVG],
|
||||||
},
|
name: [AggregateOperations.COUNT],
|
||||||
}),
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: Wrapper,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.data).toEqual({
|
expect(result.current.data).toEqual({
|
||||||
@ -74,13 +83,17 @@ describe('useAggregateRecords', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(
|
||||||
useAggregateRecords({
|
() =>
|
||||||
objectNameSingular: 'opportunity',
|
useAggregateRecords({
|
||||||
recordGqlFieldsAggregate: {
|
objectNameSingular: 'opportunity',
|
||||||
amount: [AggregateOperations.SUM],
|
recordGqlFieldsAggregate: {
|
||||||
},
|
amount: [AggregateOperations.SUM],
|
||||||
}),
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: Wrapper,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.data).toEqual({});
|
expect(result.current.data).toEqual({});
|
||||||
@ -95,13 +108,17 @@ describe('useAggregateRecords', () => {
|
|||||||
error: mockError,
|
error: mockError,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(
|
||||||
useAggregateRecords({
|
() =>
|
||||||
objectNameSingular: 'opportunity',
|
useAggregateRecords({
|
||||||
recordGqlFieldsAggregate: {
|
objectNameSingular: 'opportunity',
|
||||||
amount: [AggregateOperations.SUM],
|
recordGqlFieldsAggregate: {
|
||||||
},
|
amount: [AggregateOperations.SUM],
|
||||||
}),
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: Wrapper,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.data).toEqual({});
|
expect(result.current.data).toEqual({});
|
||||||
@ -109,14 +126,18 @@ describe('useAggregateRecords', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should skip query when specified', () => {
|
it('should skip query when specified', () => {
|
||||||
renderHook(() =>
|
renderHook(
|
||||||
useAggregateRecords({
|
() =>
|
||||||
objectNameSingular: 'opportunity',
|
useAggregateRecords({
|
||||||
recordGqlFieldsAggregate: {
|
objectNameSingular: 'opportunity',
|
||||||
amount: [AggregateOperations.SUM],
|
recordGqlFieldsAggregate: {
|
||||||
},
|
amount: [AggregateOperations.SUM],
|
||||||
skip: true,
|
},
|
||||||
}),
|
skip: true,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: Wrapper,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(useQuery).toHaveBeenCalledWith(
|
expect(useQuery).toHaveBeenCalledWith(
|
||||||
|
|||||||
@ -57,6 +57,7 @@ describe('useDeleteManyRecords', () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordId: expectedRecord.id,
|
recordId: expectedRecord.id,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
expect(cachedRecord).not.toBeNull();
|
expect(cachedRecord).not.toBeNull();
|
||||||
if (cachedRecord === null) throw new Error('Should never occur');
|
if (cachedRecord === null) throw new Error('Should never occur');
|
||||||
@ -72,6 +73,7 @@ describe('useDeleteManyRecords', () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordId,
|
recordId,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
}),
|
}),
|
||||||
).toBeNull(),
|
).toBeNull(),
|
||||||
);
|
);
|
||||||
@ -119,6 +121,7 @@ describe('useDeleteManyRecords', () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
record,
|
record,
|
||||||
}),
|
}),
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGq
|
|||||||
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||||
import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery';
|
import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -35,10 +36,16 @@ export const useAggregateRecords = <T extends AggregateRecordsData>({
|
|||||||
recordGqlFieldsAggregate,
|
recordGqlFieldsAggregate,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasReadPermission = objectPermissions.canReadObjectRecords;
|
||||||
|
|
||||||
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
|
const { data, loading, error } = useQuery<RecordGqlOperationFindManyResult>(
|
||||||
aggregateQuery,
|
aggregateQuery,
|
||||||
{
|
{
|
||||||
skip: skip || !objectMetadataItem,
|
skip: skip || !objectMetadataItem || !hasReadPermission,
|
||||||
variables: {
|
variables: {
|
||||||
filter,
|
filter,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -61,7 +62,7 @@ export const useAttachRelatedRecordFromRecord = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const updateOneRecordAndAttachRelations = async ({
|
const updateOneRecordAndAttachRelations = async ({
|
||||||
recordId,
|
recordId,
|
||||||
relatedRecordId,
|
relatedRecordId,
|
||||||
@ -98,6 +99,7 @@ export const useAttachRelatedRecordFromRecord = ({
|
|||||||
[fieldOnRelatedObject]: previousRecord,
|
[fieldOnRelatedObject]: previousRecord,
|
||||||
},
|
},
|
||||||
recordGqlFields: gqlFields,
|
recordGqlFields: gqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
|
|||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation';
|
import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldActorForInputValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
@ -70,7 +71,7 @@ export const useCreateManyRecords = <
|
|||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
});
|
});
|
||||||
@ -118,6 +119,7 @@ export const useCreateManyRecords = <
|
|||||||
...baseOptimisticRecordInputCreatedBy,
|
...baseOptimisticRecordInputCreatedBy,
|
||||||
...recordToCreate,
|
...recordToCreate,
|
||||||
},
|
},
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}),
|
}),
|
||||||
id: idForCreation as string,
|
id: idForCreation as string,
|
||||||
};
|
};
|
||||||
@ -152,6 +154,7 @@ export const useCreateManyRecords = <
|
|||||||
recordsToCreate: recordNodeCreatedInCache,
|
recordsToCreate: recordNodeCreatedInCache,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
shouldMatchRootQueryFilter,
|
shouldMatchRootQueryFilter,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +181,7 @@ export const useCreateManyRecords = <
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
shouldMatchRootQueryFilter,
|
shouldMatchRootQueryFilter,
|
||||||
checkForRecordInCache: true,
|
checkForRecordInCache: true,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
|||||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||||
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField';
|
import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
export const useCreateManyRecordsMutation = ({
|
export const useCreateManyRecordsMutation = ({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -21,6 +22,8 @@ export const useCreateManyRecordsMutation = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||||
@ -42,6 +45,7 @@ export const useCreateManyRecordsMutation = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
|
|||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
|
import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput';
|
||||||
@ -62,6 +63,7 @@ export const useCreateOneRecord = <
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
@ -90,6 +92,7 @@ export const useCreateOneRecord = <
|
|||||||
...recordInput,
|
...recordInput,
|
||||||
id: idForCreation,
|
id: idForCreation,
|
||||||
},
|
},
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
const recordCreatedInCache = createOneRecordInCache({
|
const recordCreatedInCache = createOneRecordInCache({
|
||||||
...optimisticRecordInput,
|
...optimisticRecordInput,
|
||||||
@ -113,6 +116,7 @@ export const useCreateOneRecord = <
|
|||||||
recordsToCreate: [optimisticRecordNode],
|
recordsToCreate: [optimisticRecordNode],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
shouldMatchRootQueryFilter,
|
shouldMatchRootQueryFilter,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,6 +140,7 @@ export const useCreateOneRecord = <
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
shouldMatchRootQueryFilter,
|
shouldMatchRootQueryFilter,
|
||||||
checkForRecordInCache: true,
|
checkForRecordInCache: true,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
|
|||||||
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
|
import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
export const useCreateOneRecordMutation = ({
|
export const useCreateOneRecordMutation = ({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -30,6 +31,8 @@ export const useCreateOneRecordMutation = ({
|
|||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||||
return { createOneRecordMutation: EMPTY_MUTATION };
|
return { createOneRecordMutation: EMPTY_MUTATION };
|
||||||
}
|
}
|
||||||
@ -46,6 +49,7 @@ export const useCreateOneRecordMutation = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields: appliedRecordGqlFields,
|
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 { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
|
||||||
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||||
import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation';
|
import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
|
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { sleep } from '~/utils/sleep';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { sleep } from '~/utils/sleep';
|
||||||
|
|
||||||
type useDeleteManyRecordProps = {
|
type useDeleteManyRecordProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -52,7 +53,7 @@ export const useDeleteManyRecords = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
});
|
});
|
||||||
@ -115,6 +116,7 @@ export const useDeleteManyRecords = ({
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: computedOptimisticRecord,
|
record: computedOptimisticRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
computedOptimisticRecordsNode.push(optimisticRecordNode);
|
computedOptimisticRecordsNode.push(optimisticRecordNode);
|
||||||
@ -156,6 +158,7 @@ export const useDeleteManyRecords = ({
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: { ...cachedRecord, deletedAt: null },
|
record: { ...cachedRecord, deletedAt: null },
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const cachedRecordWithConnection =
|
const cachedRecordWithConnection =
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename
|
|||||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||||
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
|
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField';
|
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField';
|
||||||
@ -37,7 +38,7 @@ export const useDeleteOneRecord = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
});
|
});
|
||||||
@ -84,6 +85,7 @@ export const useDeleteOneRecord = ({
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: computedOptimisticRecord,
|
record: computedOptimisticRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
triggerUpdateRecordOptimisticEffect({
|
triggerUpdateRecordOptimisticEffect({
|
||||||
@ -133,6 +135,7 @@ export const useDeleteOneRecord = ({
|
|||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
triggerUpdateRecordOptimisticEffect({
|
triggerUpdateRecordOptimisticEffect({
|
||||||
@ -156,6 +159,7 @@ export const useDeleteOneRecord = ({
|
|||||||
mutationResponseField,
|
mutationResponseField,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
refetchAggregateQueries,
|
refetchAggregateQueries,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,12 +8,13 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
|
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
|
||||||
import { useDestroyManyRecordsMutation } from '@/object-record/hooks/useDestroyManyRecordsMutation';
|
import { useDestroyManyRecordsMutation } from '@/object-record/hooks/useDestroyManyRecordsMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField';
|
import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { sleep } from '~/utils/sleep';
|
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
|
import { sleep } from '~/utils/sleep';
|
||||||
|
|
||||||
type useDestroyManyRecordProps = {
|
type useDestroyManyRecordProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -47,7 +48,7 @@ export const useDestroyManyRecords = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
});
|
});
|
||||||
@ -120,6 +121,7 @@ export const useDestroyManyRecords = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordsToCreate: cachedRecords,
|
recordsToCreate: cachedRecords,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField';
|
import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField';
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ export const useDestroyOneRecord = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const mutationResponseField =
|
const mutationResponseField =
|
||||||
getDestroyOneRecordMutationResponseField(objectNameSingular);
|
getDestroyOneRecordMutationResponseField(objectNameSingular);
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ export const useDestroyOneRecord = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordsToCreate: [originalRecord],
|
recordsToCreate: [originalRecord],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +91,7 @@ export const useDestroyOneRecord = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
|
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
|
||||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
|
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -17,6 +18,8 @@ export const useFindDuplicateRecordsQuery = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
const findDuplicateRecordsQuery = gql`
|
const findDuplicateRecordsQuery = gql`
|
||||||
@ -30,6 +33,7 @@ export const useFindDuplicateRecordsQuery = ({
|
|||||||
node ${mapObjectMetadataToGraphQLQuery({
|
node ${mapObjectMetadataToGraphQLQuery({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { useFetchMoreRecordsWithPagination } from '@/object-record/hooks/useFetc
|
|||||||
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
||||||
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
|
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
|
||||||
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
|
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted';
|
import { OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted';
|
||||||
import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier';
|
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' } }],
|
or: [{ deletedAt: { is: 'NULL' } }, { deletedAt: { is: 'NOT_NULL' } }],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasReadPermission = objectPermissions.canReadObjectRecords;
|
||||||
|
|
||||||
const { data, loading, error, fetchMore } =
|
const { data, loading, error, fetchMore } =
|
||||||
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||||
skip: skip || !objectMetadataItem,
|
skip: skip || !objectMetadataItem || !hasReadPermission,
|
||||||
variables: {
|
variables: {
|
||||||
filter: withSoftDeleted
|
filter: withSoftDeleted
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import {
|
import {
|
||||||
generateFindManyRecordsQuery,
|
generateFindManyRecordsQuery,
|
||||||
QueryCursorDirection,
|
QueryCursorDirection,
|
||||||
@ -25,12 +26,15 @@ export const useFindManyRecordsQuery = ({
|
|||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const findManyRecordsQuery = generateFindManyRecordsQuery({
|
const findManyRecordsQuery = generateFindManyRecordsQuery({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
cursorDirection,
|
cursorDirection,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
|||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
|
import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -38,10 +39,16 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
withSoftDeleted,
|
withSoftDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasReadPermission = objectPermissions.canReadObjectRecords;
|
||||||
|
|
||||||
const { data, loading, error } = useQuery<{
|
const { data, loading, error } = useQuery<{
|
||||||
[nameSingular: string]: RecordGqlNode;
|
[nameSingular: string]: RecordGqlNode;
|
||||||
}>(findOneRecordQuery, {
|
}>(findOneRecordQuery, {
|
||||||
skip: !objectMetadataItem || !objectRecordId || skip,
|
skip: !objectMetadataItem || !objectRecordId || skip || !hasReadPermission,
|
||||||
variables: { objectRecordId },
|
variables: { objectRecordId },
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
const recordWithoutConnection = getRecordFromRecordNode<T>({
|
const recordWithoutConnection = getRecordFromRecordNode<T>({
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const useFindOneRecordQuery = ({
|
export const useFindOneRecordQuery = ({
|
||||||
@ -22,6 +23,8 @@ export const useFindOneRecordQuery = ({
|
|||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const findOneRecordQuery = gql`
|
const findOneRecordQuery = gql`
|
||||||
query FindOne${capitalize(
|
query FindOne${capitalize(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
@ -44,6 +47,7 @@ export const useFindOneRecordQuery = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
},
|
},
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { UseFindManyRecordsParams } from '@/object-record/hooks/useFindManyRecor
|
|||||||
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
||||||
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
|
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
|
||||||
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
|
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { cursorFamilyState } from '@/object-record/states/cursorFamilyState';
|
import { cursorFamilyState } from '@/object-record/states/cursorFamilyState';
|
||||||
import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState';
|
import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
@ -55,6 +56,12 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
onCompleted,
|
onCompleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasReadPermission = objectPermissions.canReadObjectRecords;
|
||||||
|
|
||||||
const [findManyRecords, { data, loading, error, fetchMore }] =
|
const [findManyRecords, { data, loading, error, fetchMore }] =
|
||||||
useLazyQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
useLazyQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||||
variables: {
|
variables: {
|
||||||
@ -83,6 +90,20 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
const findManyRecordsLazy = useRecoilCallback(
|
const findManyRecordsLazy = useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
async () => {
|
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 result = await findManyRecords();
|
||||||
|
|
||||||
const hasNextPage =
|
const hasNextPage =
|
||||||
@ -98,19 +119,26 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
[queryIdentifier, findManyRecords, objectMetadataItem],
|
[
|
||||||
|
hasReadPermission,
|
||||||
|
findManyRecords,
|
||||||
|
objectMetadataItem.namePlural,
|
||||||
|
queryIdentifier,
|
||||||
|
onCompleted,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
records,
|
records,
|
||||||
totalCount,
|
totalCount,
|
||||||
loading,
|
loading: hasReadPermission ? loading : false,
|
||||||
error,
|
error: hasReadPermission ? error : undefined,
|
||||||
fetchMore,
|
fetchMore,
|
||||||
fetchMoreRecords,
|
fetchMoreRecords,
|
||||||
queryStateIdentifier: queryIdentifier,
|
queryStateIdentifier: queryIdentifier,
|
||||||
findManyRecords: findManyRecordsLazy,
|
findManyRecords: findManyRecordsLazy,
|
||||||
hasNextPage,
|
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 { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||||
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
|
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 { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField';
|
import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { sleep } from '~/utils/sleep';
|
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
|
import { sleep } from '~/utils/sleep';
|
||||||
|
|
||||||
type useRestoreManyRecordProps = {
|
type useRestoreManyRecordProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -50,7 +51,7 @@ export const useRestoreManyRecords = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const mutationResponseField = getRestoreManyRecordsMutationResponseField(
|
const mutationResponseField = getRestoreManyRecordsMutationResponseField(
|
||||||
objectMetadataItem.namePlural,
|
objectMetadataItem.namePlural,
|
||||||
);
|
);
|
||||||
@ -111,6 +112,7 @@ export const useRestoreManyRecords = ({
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: computedOptimisticRecord,
|
record: computedOptimisticRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
triggerUpdateRecordOptimisticEffect({
|
triggerUpdateRecordOptimisticEffect({
|
||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
@ -169,6 +171,7 @@ export const useRestoreManyRecords = ({
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: cachedRecord,
|
record: cachedRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
triggerUpdateRecordOptimisticEffect({
|
triggerUpdateRecordOptimisticEffect({
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo
|
|||||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||||
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
import { computeDepthOneRecordGqlFieldsFromRecord } from '@/object-record/graphql/utils/computeDepthOneRecordGqlFieldsFromRecord';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
@ -57,6 +58,7 @@ export const useUpdateOneRecord = <
|
|||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
@ -75,6 +77,7 @@ export const useUpdateOneRecord = <
|
|||||||
recordInput: updateOneRecordInput,
|
recordInput: updateOneRecordInput,
|
||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
const cachedRecord = getRecordFromCache<ObjectRecord>(idToUpdate);
|
const cachedRecord = getRecordFromCache<ObjectRecord>(idToUpdate);
|
||||||
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
|
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
|
||||||
@ -118,6 +121,7 @@ export const useUpdateOneRecord = <
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: computedOptimisticRecord,
|
record: computedOptimisticRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
triggerUpdateRecordOptimisticEffect({
|
triggerUpdateRecordOptimisticEffect({
|
||||||
@ -190,6 +194,7 @@ export const useUpdateOneRecord = <
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
triggerUpdateRecordOptimisticEffect({
|
triggerUpdateRecordOptimisticEffect({
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
|
|||||||
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
|
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
@ -26,6 +27,8 @@ export const useUpdateOneRecordMutation = ({
|
|||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||||
return { updateOneRecordMutation: EMPTY_MUTATION };
|
return { updateOneRecordMutation: EMPTY_MUTATION };
|
||||||
}
|
}
|
||||||
@ -44,14 +47,15 @@ export const useUpdateOneRecordMutation = ({
|
|||||||
|
|
||||||
const updateOneRecordMutation = gql`
|
const updateOneRecordMutation = gql`
|
||||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: UUID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
mutation UpdateOne${capitalizedObjectName}($idToUpdate: UUID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||||
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
|
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
|
||||||
{
|
{
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
recordGqlFields: appliedRecordGqlFields,
|
recordGqlFields: appliedRecordGqlFields,
|
||||||
},
|
objectPermissionsByObjectMetadataId,
|
||||||
)}
|
},
|
||||||
|
)}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
|||||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
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 { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
|
||||||
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||||
|
|
||||||
export const useGenerateCombinedFindManyRecordsQuery = ({
|
export const useGenerateCombinedFindManyRecordsQuery = ({
|
||||||
operationSignatures,
|
operationSignatures,
|
||||||
@ -16,6 +17,7 @@ export const useGenerateCombinedFindManyRecordsQuery = ({
|
|||||||
operationSignatures: RecordGqlOperationSignature[];
|
operationSignatures: RecordGqlOperationSignature[];
|
||||||
}) => {
|
}) => {
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
if (!isNonEmptyArray(operationSignatures)) {
|
if (!isNonEmptyArray(operationSignatures)) {
|
||||||
return null;
|
return null;
|
||||||
@ -92,6 +94,7 @@ export const useGenerateCombinedFindManyRecordsQuery = ({
|
|||||||
generateDepthOneRecordGqlFields({
|
generateDepthOneRecordGqlFields({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}),
|
}),
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getR
|
|||||||
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery';
|
||||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
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 { CombinedFindManyRecordsQueryResult } from '@/object-record/multiple-objects/types/CombinedFindManyRecordsQueryResult';
|
||||||
import { generateCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/utils/generateCombinedFindManyRecordsQueryVariables';
|
import { generateCombinedFindManyRecordsQueryVariables } from '@/object-record/multiple-objects/utils/generateCombinedFindManyRecordsQueryVariables';
|
||||||
import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
|
import { getCombinedFindManyRecordsQueryFilteringPart } from '@/object-record/multiple-objects/utils/getCombinedFindManyRecordsQueryFilteringPart';
|
||||||
@ -18,6 +19,8 @@ export const usePerformCombinedFindManyRecords = () => {
|
|||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const generateCombinedFindManyRecordsQuery = (
|
const generateCombinedFindManyRecordsQuery = (
|
||||||
operationSignatures: RecordGqlOperationSignature[],
|
operationSignatures: RecordGqlOperationSignature[],
|
||||||
objectMetadataItemsValue: ObjectMetadataItem[],
|
objectMetadataItemsValue: ObjectMetadataItem[],
|
||||||
@ -93,6 +96,7 @@ export const usePerformCombinedFindManyRecords = () => {
|
|||||||
generateDepthOneRecordGqlFields({
|
generateDepthOneRecordGqlFields({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}),
|
}),
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,6 +92,7 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
|
|||||||
return (
|
return (
|
||||||
<RecordIndexContextProvider
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
indexIdentifierUrl: () => '',
|
indexIdentifierUrl: () => '',
|
||||||
onIndexRecordsLoaded: () => {},
|
onIndexRecordsLoaded: () => {},
|
||||||
objectNamePlural: 'companies',
|
objectNamePlural: 'companies',
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
import { Draggable } from '@hello-pangea/dnd';
|
import { Draggable } from '@hello-pangea/dnd';
|
||||||
import { useContext } from 'react';
|
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 { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||||
import { RecordBoardCardFocusHotkeyEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect';
|
import { RecordBoardCardFocusHotkeyEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect';
|
||||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||||
@ -23,8 +24,11 @@ export const RecordBoardCardDraggableContainer = ({
|
|||||||
recordId: string;
|
recordId: string;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId,
|
recordId,
|
||||||
|
objectMetadataId: objectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { columnIndex } = useContext(RecordBoardColumnContext);
|
const { columnIndex } = useContext(RecordBoardColumnContext);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
|
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
|
||||||
import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown';
|
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 { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
|
||||||
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
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 { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { Tag } from 'twenty-ui/components';
|
import { Tag } from 'twenty-ui/components';
|
||||||
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
||||||
@ -95,7 +95,11 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
const { aggregateValue, aggregateLabel } =
|
const { aggregateValue, aggregateLabel } =
|
||||||
useAggregateRecordsForRecordBoardColumn();
|
useAggregateRecordsForRecordBoardColumn();
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem: objectMetadataItem,
|
objectMetadataItem: objectMetadataItem,
|
||||||
@ -143,7 +147,7 @@ export const RecordBoardColumnHeader = () => {
|
|||||||
Icon={IconDotsVertical}
|
Icon={IconDotsVertical}
|
||||||
onClick={handleBoardColumnMenuOpen}
|
onClick={handleBoardColumnMenuOpen}
|
||||||
/>
|
/>
|
||||||
{!hasObjectReadOnlyPermission && (
|
{hasObjectUpdatePermissions && (
|
||||||
<LightIconButton
|
<LightIconButton
|
||||||
accent="tertiary"
|
accent="tertiary"
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
@ -32,13 +32,17 @@ export const RecordBoardColumnNewRecordButton = () => {
|
|||||||
|
|
||||||
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
const { columnDefinition } = useContext(RecordBoardColumnContext);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem: objectMetadataItem,
|
objectMetadataItem: objectMetadataItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
if (!hasObjectUpdatePermissions) {
|
||||||
return null;
|
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 { ArrayFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ArrayFieldDisplay';
|
||||||
import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/display/components/BooleanFieldDisplay';
|
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 { 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 { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
|
||||||
import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhonesFieldDisplay';
|
import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhonesFieldDisplay';
|
||||||
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
|
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 { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||||
@ -49,13 +51,18 @@ import { isFieldText } from '../types/guards/isFieldText';
|
|||||||
import { isFieldUuid } from '../types/guards/isFieldUuid';
|
import { isFieldUuid } from '../types/guards/isFieldUuid';
|
||||||
|
|
||||||
export const FieldDisplay = () => {
|
export const FieldDisplay = () => {
|
||||||
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
|
const { fieldDefinition, isLabelIdentifier, isForbidden } =
|
||||||
|
useContext(FieldContext);
|
||||||
|
|
||||||
const isChipDisplay = isFieldIdentifierDisplay(
|
const isChipDisplay = isFieldIdentifierDisplay(
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
isLabelIdentifier,
|
isLabelIdentifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isDefined(isForbidden) && isForbidden) {
|
||||||
|
return <ForbiddenFieldDisplay />;
|
||||||
|
}
|
||||||
|
|
||||||
return isChipDisplay ? (
|
return isChipDisplay ? (
|
||||||
<ChipFieldDisplay />
|
<ChipFieldDisplay />
|
||||||
) : isFieldRelationToOneObject(fieldDefinition) ? (
|
) : isFieldRelationToOneObject(fieldDefinition) ? (
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export type GenericFieldContextType = {
|
|||||||
onOpenEditMode?: () => void;
|
onOpenEditMode?: () => void;
|
||||||
onCloseEditMode?: () => void;
|
onCloseEditMode?: () => void;
|
||||||
triggerEvent?: TriggerEventType;
|
triggerEvent?: TriggerEventType;
|
||||||
|
isForbidden?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FieldContext = createContext<GenericFieldContextType>(
|
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 { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
type UseIsRecordReadOnlyParams = {
|
type UseIsRecordReadOnlyParams = {
|
||||||
recordId: string;
|
recordId: string;
|
||||||
|
objectMetadataId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useIsRecordReadOnly = ({
|
export const useIsRecordReadOnly = ({
|
||||||
recordId,
|
recordId,
|
||||||
|
objectMetadataId,
|
||||||
}: UseIsRecordReadOnlyParams) => {
|
}: UseIsRecordReadOnlyParams) => {
|
||||||
const recordDeletedAt = useRecoilValue<ObjectRecord | null>(
|
const recordDeletedAt = useRecoilValue<ObjectRecord | null>(
|
||||||
recordStoreFamilySelector({
|
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
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
indexIdentifierUrl: () => '',
|
indexIdentifierUrl: () => '',
|
||||||
onIndexRecordsLoaded: () => {},
|
onIndexRecordsLoaded: () => {},
|
||||||
objectNamePlural: CoreObjectNamePlural.Company,
|
objectNamePlural: CoreObjectNamePlural.Company,
|
||||||
|
|||||||
@ -82,6 +82,7 @@ const RelationToOneFieldInputWithContext = ({
|
|||||||
relationObjectMetadataNamePlural: 'companies',
|
relationObjectMetadataNamePlural: 'companies',
|
||||||
relationObjectMetadataNameSingular:
|
relationObjectMetadataNameSingular:
|
||||||
CoreObjectNameSingular.Company,
|
CoreObjectNameSingular.Company,
|
||||||
|
relationObjectMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
||||||
objectMetadataNameSingular: 'person',
|
objectMetadataNameSingular: 'person',
|
||||||
relationFieldMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
relationFieldMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -131,6 +131,7 @@ export type FieldRelationMetadata = BaseFieldMetadata & {
|
|||||||
relationFieldMetadataId: string;
|
relationFieldMetadataId: string;
|
||||||
relationObjectMetadataNamePlural: string;
|
relationObjectMetadataNamePlural: string;
|
||||||
relationObjectMetadataNameSingular: string;
|
relationObjectMetadataNameSingular: string;
|
||||||
|
relationObjectMetadataId: string;
|
||||||
relationType?: RelationDefinitionType;
|
relationType?: RelationDefinitionType;
|
||||||
targetFieldMetadataName?: string;
|
targetFieldMetadataName?: string;
|
||||||
useEditButton?: boolean;
|
useEditButton?: boolean;
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewCompon
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
|
|
||||||
const StyledIndexContainer = styled.div`
|
const StyledIndexContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -57,10 +59,23 @@ export const RecordIndexContainerGater = () => {
|
|||||||
recordIndexId,
|
recordIndexId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
const objectPermissions = getObjectPermissionsForObject(
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||||
|
|
||||||
|
if (!hasObjectReadPermissions) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RecordIndexContextProvider
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNamePlural: objectMetadataItem.namePlural,
|
objectNamePlural: objectMetadataItem.namePlural,
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||||
import { createRequiredContext } from '~/utils/createRequiredContext';
|
import { createRequiredContext } from '~/utils/createRequiredContext';
|
||||||
|
|
||||||
export type RecordIndexContextValue = {
|
export type RecordIndexContextValue = {
|
||||||
@ -7,6 +8,7 @@ export type RecordIndexContextValue = {
|
|||||||
objectNamePlural: string;
|
objectNamePlural: string;
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
recordIndexId: string;
|
recordIndexId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -229,6 +229,7 @@ describe('useRecordData', () => {
|
|||||||
options: null,
|
options: null,
|
||||||
placeHolder: 'Last update',
|
placeHolder: 'Last update',
|
||||||
relationFieldMetadataId: undefined,
|
relationFieldMetadataId: undefined,
|
||||||
|
relationObjectMetadataId: '',
|
||||||
relationObjectMetadataNamePlural: '',
|
relationObjectMetadataNamePlural: '',
|
||||||
relationObjectMetadataNameSingular: '',
|
relationObjectMetadataNameSingular: '',
|
||||||
relationType: undefined,
|
relationType: undefined,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import {
|
import {
|
||||||
SingleRecordPickerMenuItems,
|
SingleRecordPickerMenuItems,
|
||||||
SingleRecordPickerMenuItemsProps,
|
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 { 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 { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState';
|
||||||
import { RecordPickerLayoutDirection } from '@/object-record/record-picker/types/RecordPickerLayoutDirection';
|
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 { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||||
@ -48,8 +49,6 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
|||||||
SingleRecordPickerComponentInstanceContext,
|
SingleRecordPickerComponentInstanceContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
const recordPickerSearchFilter = useRecoilComponentValueV2(
|
const recordPickerSearchFilter = useRecoilComponentValueV2(
|
||||||
singleRecordPickerSearchFilterComponentState,
|
singleRecordPickerSearchFilterComponentState,
|
||||||
recordPickerInstanceId,
|
recordPickerInstanceId,
|
||||||
@ -60,6 +59,16 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
|||||||
excludedRecordIds,
|
excludedRecordIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
const createNewButton = isDefined(onCreate) && (
|
const createNewButton = isDefined(onCreate) && (
|
||||||
<CreateNewButton
|
<CreateNewButton
|
||||||
onClick={() => onCreate?.(recordPickerSearchFilter)}
|
onClick={() => onCreate?.(recordPickerSearchFilter)}
|
||||||
@ -72,7 +81,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
|||||||
<>
|
<>
|
||||||
{layoutDirection === 'search-bar-on-bottom' && (
|
{layoutDirection === 'search-bar-on-bottom' && (
|
||||||
<>
|
<>
|
||||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
{isDefined(onCreate) && !hasObjectUpdatePermissions && (
|
||||||
<DropdownMenuItemsContainer scrollable={false}>
|
<DropdownMenuItemsContainer scrollable={false}>
|
||||||
{createNewButton}
|
{createNewButton}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
@ -116,7 +125,7 @@ export const SingleRecordPickerMenuItemsWithSearch = ({
|
|||||||
{records.recordsToSelect.length > 0 && isDefined(onCreate) && (
|
{records.recordsToSelect.length > 0 && isDefined(onCreate) && (
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
)}
|
)}
|
||||||
{isDefined(onCreate) && !hasObjectReadOnlyPermission && (
|
{isDefined(onCreate) && !hasObjectUpdatePermissions && (
|
||||||
<DropdownMenuItemsContainer scrollable={false}>
|
<DropdownMenuItemsContainer scrollable={false}>
|
||||||
{createNewButton}
|
{createNewButton}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
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 { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
@ -39,6 +41,7 @@ export const FieldsCard = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -87,11 +90,16 @@ export const FieldsCard = ({
|
|||||||
fieldMetadataItem.name === 'noteTargets') ||
|
fieldMetadataItem.name === 'noteTargets') ||
|
||||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||||
fieldMetadataItem.name === 'taskTargets')
|
fieldMetadataItem.name === 'taskTargets')
|
||||||
),
|
) &&
|
||||||
|
getObjectPermissionsForObject(
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
|
fieldMetadataItem.relationDefinition?.targetObjectMetadata.id,
|
||||||
|
).canReadObjectRecords,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId: objectRecordId,
|
recordId: objectRecordId,
|
||||||
|
objectMetadataId: objectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
@ -58,6 +59,10 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId,
|
objectRecordId,
|
||||||
@ -65,6 +70,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
|||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId: objectRecordId,
|
recordId: objectRecordId,
|
||||||
|
objectMetadataId: objectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { navigateToIndexView, rankInView, totalCount } =
|
const { navigateToIndexView, rankInView, totalCount } =
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
@ -55,8 +56,13 @@ export const SummaryCard = ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId: objectRecordId,
|
recordId: objectRecordId,
|
||||||
|
objectMetadataId: objectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/uti
|
|||||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import {
|
import {
|
||||||
FieldContext,
|
FieldContext,
|
||||||
@ -124,6 +125,10 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
|
||||||
|
const relationObjectPermissions = useObjectPermissionsForObject(
|
||||||
|
relationObjectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
const persistField = usePersistField();
|
const persistField = usePersistField();
|
||||||
|
|
||||||
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||||
@ -213,6 +218,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId: relationRecord.id,
|
recordId: relationRecord.id,
|
||||||
|
objectMetadataId: relationObjectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
@ -254,14 +260,15 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
text="Detach"
|
text="Detach"
|
||||||
onClick={handleDetach}
|
onClick={handleDetach}
|
||||||
/>
|
/>
|
||||||
{!isAccountOwnerRelation && (
|
{!isAccountOwnerRelation &&
|
||||||
<MenuItem
|
relationObjectPermissions.canSoftDeleteObjectRecords && (
|
||||||
LeftIcon={IconTrash}
|
<MenuItem
|
||||||
text="Delete"
|
LeftIcon={IconTrash}
|
||||||
accent="danger"
|
text="Delete"
|
||||||
onClick={handleDelete}
|
accent="danger"
|
||||||
/>
|
onClick={handleDelete}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownContent>
|
</DropdownContent>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,6 +115,7 @@ export const RecordDetailRelationSection = ({
|
|||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId,
|
recordId,
|
||||||
|
objectMetadataId: relationObjectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
|
|||||||
@ -144,6 +144,7 @@ export const RecordDetailRelationSectionDropdown = ({
|
|||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
recordId,
|
recordId,
|
||||||
|
objectMetadataId: relationObjectMetadataItem.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { RecordTableBodyEffectsWrapper } from '@/object-record/record-table/components/RecordTableBodyEffectsWrapper';
|
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';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordTable = () => {
|
export const RecordTable = () => {
|
||||||
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
|
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||||
|
useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
const tableBodyRef = useRef<HTMLTableElement>(null);
|
const tableBodyRef = useRef<HTMLTableElement>(null);
|
||||||
|
|
||||||
@ -61,12 +67,16 @@ export const RecordTable = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RecordTableBodyEffectsWrapper
|
{objectPermissions.canReadObjectRecords && (
|
||||||
hasRecordGroups={hasRecordGroups}
|
<>
|
||||||
tableBodyRef={tableBodyRef}
|
<RecordTableBodyEffectsWrapper
|
||||||
/>
|
hasRecordGroups={hasRecordGroups}
|
||||||
<RecordTableScrollToFocusedCellEffect />
|
tableBodyRef={tableBodyRef}
|
||||||
<RecordTableScrollToFocusedRowEffect />
|
/>
|
||||||
|
<RecordTableScrollToFocusedCellEffect />
|
||||||
|
<RecordTableScrollToFocusedRowEffect />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{recordTableIsEmpty && !hasRecordGroups ? (
|
{recordTableIsEmpty && !hasRecordGroups ? (
|
||||||
<RecordTableEmpty tableBodyRef={tableBodyRef} />
|
<RecordTableEmpty tableBodyRef={tableBodyRef} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -53,6 +53,7 @@ const meta: Meta = {
|
|||||||
return (
|
return (
|
||||||
<RecordIndexContextProvider
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
indexIdentifierUrl: (_recordId: string) => '',
|
indexIdentifierUrl: (_recordId: string) => '',
|
||||||
onIndexRecordsLoaded: () => {},
|
onIndexRecordsLoaded: () => {},
|
||||||
objectNamePlural: 'companies',
|
objectNamePlural: 'companies',
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
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 { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordTableEmptyState = () => {
|
export const RecordTableEmptyState = () => {
|
||||||
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||||
useRecordTableContextOrThrow();
|
useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||||
const noRecordAtAll = totalCount === 0;
|
const noRecordAtAll = totalCount === 0;
|
||||||
|
|
||||||
@ -25,7 +23,13 @@ export const RecordTableEmptyState = () => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
|
if (!hasObjectUpdatePermissions) {
|
||||||
return <RecordTableEmptyStateReadOnly />;
|
return <RecordTableEmptyStateReadOnly />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import { OnboardingStatus } from '~/generated-metadata/graphql';
|
|||||||
export const RecordTableRecordGroupBodyEffect = () => {
|
export const RecordTableRecordGroupBodyEffect = () => {
|
||||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const [hasInitialized, setHasInitialized] = useState(false);
|
||||||
|
|
||||||
const onboardingStatus = useOnboardingStatus();
|
const onboardingStatus = useOnboardingStatus();
|
||||||
|
|
||||||
const recordGroupId = useCurrentRecordGroupId();
|
const recordGroupId = useCurrentRecordGroupId();
|
||||||
@ -84,8 +86,11 @@ export const RecordTableRecordGroupBodyEffect = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
findManyRecords();
|
if (!hasInitialized) {
|
||||||
}, [onboardingStatus, findManyRecords]);
|
findManyRecords();
|
||||||
|
setHasInitialized(true);
|
||||||
|
}
|
||||||
|
}, [onboardingStatus, findManyRecords, hasInitialized]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
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 { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
@ -19,7 +22,8 @@ export const RecordTableCellFieldContextGeneric = ({
|
|||||||
useRecordTableRowContextOrThrow();
|
useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
|
||||||
|
useRecordIndexContextOrThrow();
|
||||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly({
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
@ -29,6 +33,28 @@ export const RecordTableCellFieldContextGeneric = ({
|
|||||||
|
|
||||||
const updateRecord = useContext(RecordUpdateContext);
|
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 (
|
return (
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -45,6 +71,7 @@ export const RecordTableCellFieldContextGeneric = ({
|
|||||||
}),
|
}),
|
||||||
displayedMaxRows: 1,
|
displayedMaxRows: 1,
|
||||||
isReadOnly: isFieldReadOnly,
|
isReadOnly: isFieldReadOnly,
|
||||||
|
isForbidden: !hasObjectReadPermissions,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
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 { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
@ -16,11 +18,13 @@ type RecordTableCellFieldContextLabelIdentifierProps = {
|
|||||||
export const RecordTableCellFieldContextLabelIdentifier = ({
|
export const RecordTableCellFieldContextLabelIdentifier = ({
|
||||||
children,
|
children,
|
||||||
}: RecordTableCellFieldContextLabelIdentifierProps) => {
|
}: RecordTableCellFieldContextLabelIdentifierProps) => {
|
||||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
|
||||||
|
useRecordIndexContextOrThrow();
|
||||||
const { recordId, isReadOnly: isTableRowReadOnly } =
|
const { recordId, isReadOnly: isTableRowReadOnly } =
|
||||||
useRecordTableRowContextOrThrow();
|
useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||||
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isRecordTableScrolledLeftComponent = useRecoilComponentValueV2(
|
const isRecordTableScrolledLeftComponent = useRecoilComponentValueV2(
|
||||||
@ -32,6 +36,13 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
|||||||
isRecordReadOnly: isTableRowReadOnly ?? false,
|
isRecordReadOnly: isTableRowReadOnly ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const objectPermissions = getObjectPermissionsForObject(
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
|
||||||
|
|
||||||
const updateRecord = useContext(RecordUpdateContext);
|
const updateRecord = useContext(RecordUpdateContext);
|
||||||
|
|
||||||
const isLabelIdentifierCompact =
|
const isLabelIdentifierCompact =
|
||||||
@ -49,6 +60,7 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
|
|||||||
displayedMaxRows: 1,
|
displayedMaxRows: 1,
|
||||||
isReadOnly: isFieldReadOnly,
|
isReadOnly: isFieldReadOnly,
|
||||||
maxWidth: columnDefinition.size,
|
maxWidth: columnDefinition.size,
|
||||||
|
isForbidden: !hasObjectReadPermissions,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
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 { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
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 { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||||
import { PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
import { PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
@ -223,7 +223,11 @@ export const RecordTableHeaderCell = ({
|
|||||||
|
|
||||||
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
const isFirstRowActive = useRecoilComponentFamilyValueV2(
|
const isFirstRowActive = useRecoilComponentFamilyValueV2(
|
||||||
isRecordTableRowActiveComponentFamilyState,
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
@ -256,7 +260,7 @@ export const RecordTableHeaderCell = ({
|
|||||||
{(useIsMobile() || iconVisibility) &&
|
{(useIsMobile() || iconVisibility) &&
|
||||||
!!column.isLabelIdentifier &&
|
!!column.isLabelIdentifier &&
|
||||||
!isReadOnly &&
|
!isReadOnly &&
|
||||||
!hasObjectReadOnlyPermission && (
|
hasObjectUpdatePermissions && (
|
||||||
<StyledHeaderIcon>
|
<StyledHeaderIcon>
|
||||||
<LightIconButton
|
<LightIconButton
|
||||||
Icon={IconPlus}
|
Icon={IconPlus}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
|
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
|
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@ -23,8 +23,6 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
|||||||
recordGroupDefinitionFamilyState(currentRecordGroupId),
|
recordGroupDefinitionFamilyState(currentRecordGroupId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
});
|
});
|
||||||
@ -33,7 +31,13 @@ export const RecordTableRecordGroupSectionAddNew = () => {
|
|||||||
(field) => field.id === recordGroup?.fieldMetadataId,
|
(field) => field.id === recordGroup?.fieldMetadataId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
const objectPermissions = useObjectPermissionsForObject(
|
||||||
|
objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||||
|
|
||||||
|
if (!hasObjectUpdatePermissions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -47,6 +48,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
createdBy: actorFieldValueForInput,
|
createdBy: actorFieldValueForInput,
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -75,6 +77,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
} satisfies FieldActorForInputValue,
|
} satisfies FieldActorForInputValue,
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -100,6 +103,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
companyId: '123',
|
companyId: '123',
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -133,6 +137,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
cache,
|
cache,
|
||||||
record: companyRecord,
|
record: companyRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = computeOptimisticRecordFromInput({
|
const result = computeOptimisticRecordFromInput({
|
||||||
@ -144,6 +149,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
__typename: 'test',
|
__typename: 'test',
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toStrictEqual({
|
expect(result).toStrictEqual({
|
||||||
@ -178,6 +184,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
cache,
|
cache,
|
||||||
record: companyRecord,
|
record: companyRecord,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = computeOptimisticRecordFromInput({
|
const result = computeOptimisticRecordFromInput({
|
||||||
@ -188,6 +195,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
companyId: '123',
|
companyId: '123',
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -208,6 +216,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
companyId: null,
|
companyId: null,
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -232,6 +241,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Should never occur, encountered unknown fields unknwon, foo, bar in objectMetadataItem person"`,
|
`"Should never occur, encountered unknown fields unknwon, foo, bar in objectMetadataItem person"`,
|
||||||
@ -252,6 +262,7 @@ describe('computeOptimisticRecordFromInput', () => {
|
|||||||
company: {},
|
company: {},
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`,
|
`"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,
|
company: null,
|
||||||
},
|
},
|
||||||
cache,
|
cache,
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
}),
|
}),
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Should never provide relation mutation through anything else than the fieldId e.g companyId and not company, encountered: company"`,
|
`"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;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
recordInput: Partial<ObjectRecord>;
|
recordInput: Partial<ObjectRecord>;
|
||||||
currentWorkspaceMember: CurrentWorkspaceMember | null;
|
currentWorkspaceMember: CurrentWorkspaceMember | null;
|
||||||
} & Pick<GetRecordFromCacheArgs, 'cache' | 'objectMetadataItems'>;
|
} & Pick<
|
||||||
|
GetRecordFromCacheArgs,
|
||||||
|
'cache' | 'objectMetadataItems' | 'objectPermissionsByObjectMetadataId'
|
||||||
|
>;
|
||||||
export const computeOptimisticRecordFromInput = ({
|
export const computeOptimisticRecordFromInput = ({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordInput,
|
recordInput,
|
||||||
cache,
|
cache,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
currentWorkspaceMember,
|
currentWorkspaceMember,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: ComputeOptimisticCacheRecordInputArgs) => {
|
}: ComputeOptimisticCacheRecordInputArgs) => {
|
||||||
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
||||||
(recordKey) => {
|
(recordKey) => {
|
||||||
@ -177,6 +181,7 @@ export const computeOptimisticRecordFromInput = ({
|
|||||||
objectMetadataItem: targetObjectMetataDataItem,
|
objectMetadataItem: targetObjectMetataDataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
recordId: recordInputFieldIdValue as string,
|
recordId: recordInputFieldIdValue as string,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
optimisticRecord[relationFieldIdName] = recordInputFieldIdValue;
|
optimisticRecord[relationFieldIdName] = recordInputFieldIdValue;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|||||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export type QueryCursorDirection = 'before' | 'after';
|
export type QueryCursorDirection = 'before' | 'after';
|
||||||
|
|
||||||
@ -13,12 +14,14 @@ export const generateFindManyRecordsQuery = ({
|
|||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
cursorDirection,
|
cursorDirection,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
objectMetadataItems: ObjectMetadataItem[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||||
computeReferences?: boolean;
|
computeReferences?: boolean;
|
||||||
cursorDirection?: QueryCursorDirection;
|
cursorDirection?: QueryCursorDirection;
|
||||||
|
objectPermissionsByObjectMetadataId: Record<string, ObjectPermission>;
|
||||||
}) => gql`
|
}) => gql`
|
||||||
query FindMany${capitalize(
|
query FindMany${capitalize(
|
||||||
objectMetadataItem.namePlural,
|
objectMetadataItem.namePlural,
|
||||||
@ -38,6 +41,7 @@ query FindMany${capitalize(
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
})}
|
})}
|
||||||
cursor
|
cursor
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,7 @@ export const SignInBackgroundMockContainer = () => {
|
|||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<RecordIndexContextProvider
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
recordIndexId,
|
recordIndexId,
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
objectNameSingular,
|
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 { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||||
import { DELETED_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/deletedWorkspaceMemberQueryFragment';
|
import { DELETED_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/deletedWorkspaceMemberQueryFragment';
|
||||||
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
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`
|
export const USER_QUERY_FRAGMENT = gql`
|
||||||
${ROLE_FRAGMENT}
|
${ROLE_FRAGMENT}
|
||||||
|
${OBJECT_PERMISSION_FRAGMENT}
|
||||||
fragment UserQueryFragment on User {
|
fragment UserQueryFragment on User {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
@ -26,6 +28,9 @@ export const USER_QUERY_FRAGMENT = gql`
|
|||||||
currentUserWorkspace {
|
currentUserWorkspace {
|
||||||
settingsPermissions
|
settingsPermissions
|
||||||
objectRecordsPermissions
|
objectRecordsPermissions
|
||||||
|
objectPermissions {
|
||||||
|
...ObjectPermissionFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
currentWorkspace {
|
currentWorkspace {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -97,6 +97,7 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
|
|||||||
return (
|
return (
|
||||||
<RecordIndexContextProvider
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
|
objectPermissionsByObjectMetadataId: {},
|
||||||
indexIdentifierUrl: () => '',
|
indexIdentifierUrl: () => '',
|
||||||
onIndexRecordsLoaded: () => {},
|
onIndexRecordsLoaded: () => {},
|
||||||
objectNamePlural: CoreObjectNamePlural.Company,
|
objectNamePlural: CoreObjectNamePlural.Company,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||||
import { ViewField } from '@/views/types/ViewField';
|
import { ViewField } from '@/views/types/ViewField';
|
||||||
@ -33,7 +34,7 @@ export const usePersistViewFieldRecords = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const createViewFieldRecords = useCallback(
|
const createViewFieldRecords = useCallback(
|
||||||
@ -62,6 +63,7 @@ export const usePersistViewFieldRecords = () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordsToCreate: [record],
|
recordsToCreate: [record],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -73,6 +75,7 @@ export const usePersistViewFieldRecords = () => {
|
|||||||
createOneRecordMutation,
|
createOneRecordMutation,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||||
@ -37,7 +38,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const createViewFilterGroupRecord = useCallback(
|
const createViewFilterGroupRecord = useCallback(
|
||||||
@ -65,6 +66,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordsToCreate: [record],
|
recordsToCreate: [record],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -80,6 +82,7 @@ export const usePersistViewFilterGroupRecords = () => {
|
|||||||
createOneRecordMutation,
|
createOneRecordMutation,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
@ -37,7 +38,7 @@ export const usePersistViewFilterRecords = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const createViewFilterRecords = useCallback(
|
const createViewFilterRecords = useCallback(
|
||||||
@ -70,6 +71,7 @@ export const usePersistViewFilterRecords = () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordsToCreate: [record],
|
recordsToCreate: [record],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -81,6 +83,7 @@ export const usePersistViewFilterRecords = () => {
|
|||||||
createOneRecordMutation,
|
createOneRecordMutation,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||||
import { ViewSort } from '@/views/types/ViewSort';
|
import { ViewSort } from '@/views/types/ViewSort';
|
||||||
@ -37,7 +38,7 @@ export const usePersistViewSortRecords = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const createViewSortRecords = useCallback(
|
const createViewSortRecords = useCallback(
|
||||||
@ -64,6 +65,7 @@ export const usePersistViewSortRecords = () => {
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
recordsToCreate: [record],
|
recordsToCreate: [record],
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -75,6 +77,7 @@ export const usePersistViewSortRecords = () => {
|
|||||||
createOneRecordMutation,
|
createOneRecordMutation,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObje
|
|||||||
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||||
|
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
@ -45,6 +46,8 @@ export const useViewFromQueryParams = () => {
|
|||||||
|
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
|
|
||||||
const queryParamsValidation = filterQueryParamsSchema.safeParse(
|
const queryParamsValidation = filterQueryParamsSchema.safeParse(
|
||||||
qs.parse(searchParams.toString()),
|
qs.parse(searchParams.toString()),
|
||||||
);
|
);
|
||||||
@ -122,6 +125,7 @@ export const useViewFromQueryParams = () => {
|
|||||||
query: generateFindManyRecordsQuery({
|
query: generateFindManyRecordsQuery({
|
||||||
objectMetadataItem: relationObjectMetadataItem,
|
objectMetadataItem: relationObjectMetadataItem,
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
objectPermissionsByObjectMetadataId,
|
||||||
}),
|
}),
|
||||||
variables: {
|
variables: {
|
||||||
filter: {
|
filter: {
|
||||||
@ -182,6 +186,7 @@ export const useViewFromQueryParams = () => {
|
|||||||
hasFiltersQueryParams,
|
hasFiltersQueryParams,
|
||||||
objectMetadataItem.fields,
|
objectMetadataItem.fields,
|
||||||
objectMetadataItems,
|
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 { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
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 { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
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 { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
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 { DELETE_WORKFLOW_VERSION_STEP } from '@/workflow/graphql/mutations/deleteWorkflowVersionStep';
|
||||||
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
import { WorkflowVersion } from '@/workflow/types/Workflow';
|
||||||
import { useApolloClient, useMutation } from '@apollo/client';
|
import { useApolloClient, useMutation } from '@apollo/client';
|
||||||
@ -17,6 +18,7 @@ import {
|
|||||||
export const useDeleteWorkflowVersionStep = () => {
|
export const useDeleteWorkflowVersionStep = () => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
const { objectMetadataItems } = useObjectMetadataItems();
|
const { objectMetadataItems } = useObjectMetadataItems();
|
||||||
|
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
||||||
});
|
});
|
||||||
@ -68,6 +70,7 @@ export const useDeleteWorkflowVersionStep = () => {
|
|||||||
cache: apolloClient.cache,
|
cache: apolloClient.cache,
|
||||||
record: newCachedRecord,
|
record: newCachedRecord,
|
||||||
recordGqlFields,
|
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