Skip to content

Commit

Permalink
Add possibility to destroy a record (twentyhq#9144)
Browse files Browse the repository at this point in the history
There are two follow ups to this PR:
- Bug: sometimes when opening Cmd+K from a deleted record, we are facing
a global error
- On Index page, actions in top right are displaying label and not short
name
- Implement multiple actions once refactoring on delete is complete

---------

Co-authored-by: bosiraphael <[email protected]>
  • Loading branch information
2 people authored and mdrazak2001 committed Dec 20, 2024
1 parent ad84cc5 commit 8bb30b4
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
import { DELETE_MAX_COUNT } from '@/object-record/constants/DeleteMaxCount';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useState } from 'react';
import { IconTrash, isDefined } from 'twenty-ui';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';

export const useDeleteMultipleRecordsAction = ({
objectMetadataItem,
Expand Down Expand Up @@ -118,7 +118,8 @@ export const useDeleteMultipleRecordsAction = ({
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'delete-multiple-records',
label: 'Delete',
label: 'Delete records',
shortLabel: 'Delete',
position,
Icon: IconTrash,
accent: 'danger',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const useExportMultipleRecordsAction = ({
key: 'export-multiple-records',
position,
label: displayedExportProgress(progress),
shortLabel: 'Export',
Icon: IconDatabaseExport,
accent: 'default',
onClick: () => download(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useDestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction';
import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToNextRecordSingleRecordAction';
import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
Expand All @@ -16,6 +17,7 @@ import {
IconHeart,
IconHeartOff,
IconTrash,
IconTrashX,
} from 'twenty-ui';

export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
Expand Down Expand Up @@ -70,13 +72,29 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
],
actionHook: useDeleteSingleRecordAction,
},
destroySingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'destroy-single-record',
label: 'Permanently destroy record',
shortLabel: 'Destroy',
position: 3,
Icon: IconTrashX,
accent: 'danger',
isPinned: true,
availableOn: [
ActionAvailableOn.INDEX_PAGE_SINGLE_RECORD_SELECTION,
ActionAvailableOn.SHOW_PAGE,
],
actionHook: useDestroySingleRecordAction,
},
navigateToPreviousRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: 'navigate-to-previous-record',
label: 'Navigate to previous record',
shortLabel: '',
position: 3,
position: 4,
isPinned: true,
Icon: IconChevronUp,
availableOn: [ActionAvailableOn.SHOW_PAGE],
Expand All @@ -88,7 +106,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
key: 'navigate-to-next-record',
label: 'Navigate to next record',
shortLabel: '',
position: 4,
position: 5,
isPinned: true,
Icon: IconChevronDown,
availableOn: [ActionAvailableOn.SHOW_PAGE],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/acti
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { isNull } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';

Expand All @@ -23,7 +24,8 @@ export const useAddToFavoritesSingleRecordAction: SingleRecordActionHookWithObje
isDefined(objectMetadataItem) &&
isDefined(selectedRecord) &&
!objectMetadataItem.isRemote &&
!isFavorite;
!isFavorite &&
isNull(selectedRecord.deletedAt);

const onClick = () => {
if (!shouldBeRegistered) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { isNull } from '@sniptt/guards';
import { useCallback, useContext, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';

export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
Expand All @@ -22,6 +25,8 @@ export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetada
objectNameSingular: objectMetadataItem.nameSingular,
});

const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));

const { sortedFavorites: favorites } = useFavorites();
const { deleteFavorite } = useDeleteFavorite();

Expand Down Expand Up @@ -52,7 +57,8 @@ export const useDeleteSingleRecordAction: SingleRecordActionHookWithObjectMetada
const { isInRightDrawer, onActionExecutedCallback } =
useContext(ActionMenuContext);

const shouldBeRegistered = !isRemoteObject;
const shouldBeRegistered =
!isRemoteObject && isNull(selectedRecord?.deletedAt);

const onClick = () => {
if (!shouldBeRegistered) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { SingleRecordActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/singleRecordActionHook';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { useCallback, useContext, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';

export const useDestroySingleRecordAction: SingleRecordActionHookWithObjectMetadataItem =
({ recordId, objectMetadataItem }) => {
const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] =
useState(false);

const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural,
});

const { destroyOneRecord } = useDestroyOneRecord({
objectNameSingular: objectMetadataItem.nameSingular,
});

const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));

const { closeRightDrawer } = useRightDrawer();

const handleDeleteClick = useCallback(async () => {
resetTableRowSelection();

await destroyOneRecord(recordId);
}, [resetTableRowSelection, destroyOneRecord, recordId]);

const isRemoteObject = objectMetadataItem.isRemote;

const { isInRightDrawer, onActionExecutedCallback } =
useContext(ActionMenuContext);

const shouldBeRegistered =
!isRemoteObject && isDefined(selectedRecord?.deletedAt);

const onClick = () => {
if (!shouldBeRegistered) {
return;
}

setIsDestroyRecordsModalOpen(true);
};

return {
shouldBeRegistered,
onClick,
ConfirmationModal: (
<ConfirmationModal
isOpen={isDestroyRecordsModalOpen}
setIsOpen={setIsDestroyRecordsModalOpen}
title={'Permanently Destroy Record'}
subtitle={
'Are you sure you want to destroy this record? It cannot be recovered anymore.'
}
onConfirmClick={async () => {
await handleDeleteClick();
onActionExecutedCallback?.();
if (isInRightDrawer) {
closeRightDrawer();
}
}}
deleteButtonText={'Permanently Destroy Record'}
/>
),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const RecordIndexActionMenuButtons = () => {
size="small"
variant="secondary"
accent="default"
title={entry.label}
title={entry.shortLabel}
onClick={() => entry.onClick?.()}
ariaLabel={entry.label}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const useFindManyRecordsSelectedInContextStore = ({
const { records, loading, totalCount } = useFindManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
filter: queryFilter,
withSoftDeleted: true,
orderBy: [
{
position: 'AscNullsFirst',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type UseFindManyRecordsParams<T> = ObjectMetadataItemIdentifier &
skip?: boolean;
recordGqlFields?: RecordGqlOperationGqlRecordFields;
fetchPolicy?: WatchQueryFetchPolicy;
withSoftDeleted?: boolean;
};

export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
Expand All @@ -33,6 +34,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
onError,
onCompleted,
cursorFilter,
withSoftDeleted = false,
}: UseFindManyRecordsParams<T>) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
Expand Down Expand Up @@ -61,11 +63,18 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
onCompleted,
});

const withSoftDeleterFilter = {
or: [{ deletedAt: { is: 'NULL' } }, { deletedAt: { is: 'NOT_NULL' } }],
};

const { data, loading, error, fetchMore } =
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
skip: skip || !objectMetadataItem,
variables: {
filter,
filter: {
...filter,
...(withSoftDeleted ? withSoftDeleterFilter : {}),
},
orderBy,
lastCursor: cursorFilter?.cursor ?? undefined,
limit: cursorFilter?.limit ?? limit,
Expand Down
1 change: 1 addition & 0 deletions packages/twenty-front/src/testing/mock-data/people.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const peopleQueryResult: { people: RecordGqlConnection } = {
__typename: 'Person',
createdAt: '2024-08-02T09:52:46.814Z',
city: 'ASd',
deletedAt: null,
phones: {
primaryPhoneNumber: '781234562',
primaryPhoneCountryCode: 'FR',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export {
IconTextWrap,
IconTimelineEvent,
IconTrash,
IconTrashX,
IconUnlink,
IconUpload,
IconUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const StyledButton = styled.button<
border-color: ${variant === 'secondary'
? !disabled && focus
? theme.color.blue
: theme.background.transparent.light
: theme.background.transparent.medium
: focus
? theme.color.blue
: 'transparent'};
Expand Down

0 comments on commit 8bb30b4

Please sign in to comment.