From 126b185e1d07770dc09a6fb511865a215d8950e6 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Wed, 9 Oct 2024 15:36:58 +0200 Subject: [PATCH 1/7] Make workflow objects readOnly --- .../hooks/useObjectIsRemote.ts | 5 -- .../types/CoreObjectNameSingular.ts | 1 + .../object-metadata/utils/isReadOnlyObject.ts | 8 ++ .../utils/isWorkflowSubObject.ts | 5 ++ .../record-field/hooks/useIsFieldReadOnly.ts | 9 +- .../isFieldReadonlyFromObjectMetadataName.ts | 17 ++++ .../components/RecordIndexPageHeader.tsx | 19 ++-- .../RecordDetailRelationRecordsListItem.tsx | 64 +++++++------ .../RecordDetailRelationSection.tsx | 90 ++++++++++--------- .../components/RecordTableEmptyState.tsx | 3 +- .../components/ShowPageMoreButton.tsx | 12 ++- 11 files changed, 146 insertions(+), 87 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/isReadOnlyObject.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObject.ts create mode 100644 packages/twenty-front/src/modules/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName.ts diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts deleted file mode 100644 index 0f3295dc5c29..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectIsRemote.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; - -export const useObjectIsRemote = (objectMetadataItem: ObjectMetadataItem) => { - return objectMetadataItem.isRemote ?? false; -}; diff --git a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts index dd496e70d3d2..8ac533f76aac 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts @@ -31,4 +31,5 @@ export enum CoreObjectNameSingular { Workflow = 'workflow', MessageChannelMessageAssociation = 'messageChannelMessageAssociation', WorkflowVersion = 'workflowVersion', + WorkflowRun = 'workflowRun', } diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isReadOnlyObject.ts b/packages/twenty-front/src/modules/object-metadata/utils/isReadOnlyObject.ts new file mode 100644 index 000000000000..6d57e85f384b --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/isReadOnlyObject.ts @@ -0,0 +1,8 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { isWorkflowSubObject } from '@/object-metadata/utils/isWorkflowSubObject'; + +export const isReadOnlyObject = ( + objectMetadataItem: Pick, +) => + objectMetadataItem.isRemote || + isWorkflowSubObject(objectMetadataItem.nameSingular); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObject.ts b/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObject.ts new file mode 100644 index 000000000000..a02d78f3e635 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/isWorkflowSubObject.ts @@ -0,0 +1,5 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; + +export const isWorkflowSubObject = (objectMetadataNameSingular?: string) => + objectMetadataNameSingular === CoreObjectNameSingular.WorkflowVersion || + objectMetadataNameSingular === CoreObjectNameSingular.WorkflowRun; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts index e4e4970c0b0d..6535b73a867b 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldReadOnly.ts @@ -2,15 +2,22 @@ import { useContext } from 'react'; import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor'; import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText'; +import { isFieldReadonlyFromObjectMetadataName } from '@/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName'; import { FieldContext } from '../contexts/FieldContext'; export const useIsFieldReadOnly = () => { const { fieldDefinition } = useContext(FieldContext); + const metadata = fieldDefinition.metadata; + return ( fieldDefinition.metadata.fieldName === 'noteTargets' || fieldDefinition.metadata.fieldName === 'taskTargets' || isFieldActor(fieldDefinition) || - isFieldRichText(fieldDefinition) + isFieldRichText(fieldDefinition) || + isFieldReadonlyFromObjectMetadataName( + metadata.fieldName, + metadata.objectMetadataNameSingular, + ) ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName.ts new file mode 100644 index 000000000000..ca208cbccf70 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName.ts @@ -0,0 +1,17 @@ +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { isWorkflowSubObject } from '@/object-metadata/utils/isWorkflowSubObject'; + +export const isFieldReadonlyFromObjectMetadataName = ( + fieldName: string, + objectMetadataNameSingular?: string, +) => { + if (!objectMetadataNameSingular) { + return false; + } + + return ( + isWorkflowSubObject(objectMetadataNameSingular) || + (objectMetadataNameSingular === CoreObjectNameSingular.Workflow && + fieldName !== 'name') + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx index bb8c0197a940..bb0f3cf01168 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx @@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; +import { isReadOnlyObject } from '@/object-metadata/utils/isReadOnlyObject'; import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; @@ -30,8 +31,11 @@ export const RecordIndexPageHeader = () => { const recordIndexViewType = useRecoilValue(recordIndexViewTypeState); - const isTable = - recordIndexViewType === ViewType.Table && !objectMetadataItem?.isRemote; + const shouldDisplayAddButton = objectMetadataItem + ? !isReadOnlyObject(objectMetadataItem) + : false; + + const isTable = recordIndexViewType === ViewType.Table; const pageHeaderTitle = objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural); @@ -43,11 +47,12 @@ export const RecordIndexPageHeader = () => { return ( - {isTable ? ( - - ) : ( - - )} + {shouldDisplayAddButton && + (isTable ? ( + + ) : ( + + ))} ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx index 36a3d7a1a2fe..aaa3d179ce17 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx @@ -24,6 +24,7 @@ import { } from '@/object-record/record-field/contexts/FieldContext'; import { usePersistField } from '@/object-record/record-field/hooks/usePersistField'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldReadonlyFromObjectMetadataName } from '@/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; @@ -180,6 +181,11 @@ export const RecordDetailRelationRecordsListItem = ({ [isExpanded], ); + const isReadOnly = !isFieldReadonlyFromObjectMetadataName( + fieldDefinition.metadata.fieldName, + fieldDefinition.metadata.objectMetadataNameSingular, + ); + return ( <> @@ -195,37 +201,39 @@ export const RecordDetailRelationRecordsListItem = ({ accent="tertiary" /> - - - } - dropdownComponents={ - - + - {!isAccountOwnerRelation && ( + } + dropdownComponents={ + - )} - - } - dropdownHotkeyScope={{ scope: dropdownScopeId }} - /> - + {!isAccountOwnerRelation && ( + + )} + + } + dropdownHotkeyScope={{ scope: dropdownScopeId }} + /> + + )} diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx index 4e2d194036f6..385469f6e7fd 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx @@ -12,6 +12,7 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi import { RelationFromManyFieldInputMultiRecordsEffect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput'; import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { isFieldReadonlyFromObjectMetadataName } from '@/object-record/record-field/utils/isFieldReadonlyFromObjectMetadataName'; import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList'; import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection'; import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader'; @@ -158,6 +159,11 @@ export const RecordDetailRelationSection = ({ recordId, }); + const isReadOnly = !isFieldReadonlyFromObjectMetadataName( + fieldDefinition.metadata.fieldName, + fieldDefinition.metadata.objectMetadataNameSingular, + ); + if (loading) return null; return ( @@ -178,49 +184,51 @@ export const RecordDetailRelationSection = ({ hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile} areRecordsAvailable={relationRecords.length > 0} rightAdornment={ - - - } - dropdownComponents={ - - {isToOneObject ? ( - - ) : ( - <> - - - + + } + dropdownComponents={ + + {isToOneObject ? ( + - - )} - - } - dropdownHotkeyScope={{ - scope: dropdownId, - }} - /> - + ) : ( + <> + + + + + )} + + } + dropdownHotkeyScope={{ + scope: dropdownId, + }} + /> + + ) } /> {showContent()} diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx index c25a3cf11990..7ea1deb12616 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyState.tsx @@ -1,4 +1,3 @@ -import { useObjectIsRemote } from '@/object-metadata/hooks/useObjectIsRemote'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll'; @@ -18,7 +17,7 @@ export const RecordTableEmptyState = () => { const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 }); const noRecordAtAll = totalCount === 0; - const isRemote = useObjectIsRemote(objectMetadataItem); + const isRemote = objectMetadataItem.isRemote; const isSoftDeleteActive = useRecoilValue(isSoftDeleteActiveState); diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx index 30efbdf71fa2..169b4d5a57b8 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx @@ -11,6 +11,8 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { isReadOnlyObject } from '@/object-metadata/utils/isReadOnlyObject'; import { useDestroyManyRecords } from '@/object-record/hooks/useDestroyManyRecords'; import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; @@ -41,7 +43,9 @@ export const ShowPageMoreButton = ({ const { restoreManyRecords } = useRestoreManyRecords({ objectNameSingular, }); - + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular, + }); const handleDelete = () => { deleteOneRecord(recordId); closeDropdown(); @@ -62,6 +66,8 @@ export const ShowPageMoreButton = ({ recordStoreFamilyState(recordId), ); + const isReadOnly = isReadOnlyObject(objectMetadataItem); + return ( - {recordFromStore && !recordFromStore.deletedAt && ( + {!isReadOnly && recordFromStore && !recordFromStore.deletedAt && ( )} - {recordFromStore && recordFromStore.deletedAt && ( + {!isReadOnly && recordFromStore && recordFromStore.deletedAt && ( <> Date: Wed, 9 Oct 2024 17:08:44 +0200 Subject: [PATCH 2/7] Remove add button on empty state + set delete cascade on workflow sub objects --- .../RecordTableEmptyStateDisplay.tsx | 20 +++++++++++++------ .../workflow.workspace-entity.ts | 6 +++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx index 80c5ecaefeb1..1dcb489a79ea 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay.tsx @@ -8,7 +8,10 @@ import { AnimatedPlaceholderEmptyTitle, } from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled'; +import { isReadOnlyObject } from '@/object-metadata/utils/isReadOnlyObject'; +import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { Button } from '@/ui/input/button/components/Button'; +import { useContext } from 'react'; import { IconComponent } from 'twenty-ui'; type RecordTableEmptyStateDisplayProps = { @@ -28,6 +31,9 @@ export const RecordTableEmptyStateDisplay = ({ subTitle, title, }: RecordTableEmptyStateDisplayProps) => { + const { objectMetadataItem } = useContext(RecordTableContext); + const isReadOnly = isReadOnlyObject(objectMetadataItem); + return ( @@ -37,12 +43,14 @@ export const RecordTableEmptyStateDisplay = ({ {subTitle} -