Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: soft delete #6576

Merged
merged 25 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4eb1232
feat: wip soft delete
magrinj Aug 7, 2024
1b3b1d4
feat: soft delete
magrinj Aug 8, 2024
9097cae
fix: typing issue
magrinj Aug 8, 2024
6396ad4
fix: remove unused standard id
magrinj Aug 8, 2024
abe16d1
fix: rename soft delete in object metadata entity
magrinj Aug 8, 2024
c1b2129
fix: typo
magrinj Aug 8, 2024
50c5759
fix: rename workspace custom decorator
magrinj Aug 8, 2024
286b6c5
feat: wip nested filtering
magrinj Aug 8, 2024
58f94b0
feat: soft delete front-end wip
magrinj Aug 9, 2024
dff602f
feat: working trash filter button
magrinj Aug 12, 2024
8678e81
Merge branch 'main' into feat/soft-delete
FelixMalfait Aug 13, 2024
305ae89
Fix show page not displaying deleted records
FelixMalfait Aug 13, 2024
3b62a3d
Rename variants
FelixMalfait Aug 13, 2024
fc32b37
Add logs and timeline activity for deletion
FelixMalfait Aug 13, 2024
56f11b2
Remove createdAt and deletedAt field from showpage
FelixMalfait Aug 13, 2024
d8490ef
Begin displaying header bar and migrate settings to new display
FelixMalfait Aug 14, 2024
4391112
Temp restore/destroy
FelixMalfait Aug 15, 2024
307b946
Add restoreMany and destroyMany
FelixMalfait Aug 15, 2024
4c0ba58
Merge main
FelixMalfait Aug 16, 2024
1315527
Remove restored event
FelixMalfait Aug 16, 2024
388d66b
Merge branch 'main' into feat/soft-delete
lucasbordeau Aug 16, 2024
975ad41
Fixed vite
lucasbordeau Aug 16, 2024
8c77f57
Fixed GQL schema generation bug and forced refetch query
lucasbordeau Aug 16, 2024
693e6d4
Fixed lint
lucasbordeau Aug 16, 2024
d5fc22a
Fixed lint
lucasbordeau Aug 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions packages/twenty-front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,9 @@ export type FieldConnection = {

/** Type of the field */
export enum FieldMetadataType {
Actor = 'ACTOR',
Address = 'ADDRESS',
Boolean = 'BOOLEAN',
Actor = 'ACTOR',
Currency = 'CURRENCY',
Date = 'DATE',
DateTime = 'DATE_TIME',
Expand Down Expand Up @@ -452,13 +452,13 @@ export type Mutation = {
generateTransientToken: TransientToken;
impersonate: Verify;
renewToken: AuthTokens;
runWorkflowVersion: WorkflowTriggerResult;
sendInviteLink: SendInviteLink;
signUp: LoginToken;
skipSyncEmailOnboardingStep: OnboardingStepSuccess;
syncRemoteTable: RemoteTable;
syncRemoteTableSchemaChanges: RemoteTable;
track: Analytics;
triggerWorkflow: WorkflowTriggerResult;
unsyncRemoteTable: RemoteTable;
updateBillingSubscription: UpdateBillingEntity;
updateOneField: Field;
Expand Down Expand Up @@ -610,6 +610,11 @@ export type MutationRenewTokenArgs = {
};


export type MutationRunWorkflowVersionArgs = {
input: RunWorkflowVersionInput;
};


export type MutationSendInviteLinkArgs = {
emails: Array<Scalars['String']['input']>;
};
Expand Down Expand Up @@ -639,11 +644,6 @@ export type MutationTrackArgs = {
};


export type MutationTriggerWorkflowArgs = {
workflowVersionId: Scalars['String']['input'];
};


export type MutationUnsyncRemoteTableArgs = {
input: RemoteTableInput;
};
Expand Down Expand Up @@ -1001,6 +1001,13 @@ export enum RemoteTableStatus {
Synced = 'SYNCED'
}

export type RunWorkflowVersionInput = {
/** Execution result in JSON format */
payload?: InputMaybe<Scalars['JSON']['input']>;
/** Workflow version ID */
workflowVersionId: Scalars['String']['input'];
};

export type SendInviteLink = {
__typename?: 'SendInviteLink';
/** Boolean that confirms query was dispatched */
Expand Down Expand Up @@ -1400,6 +1407,7 @@ export type WorkspaceFeatureFlagsArgs = {
export enum WorkspaceActivationStatus {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
OngoingCreation = 'ONGOING_CREATION',
PendingCreation = 'PENDING_CREATION'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FilterDefinition } from './FilterDefinition';

export type Filter = {
id: string;
variant?: 'default' | 'trash';
FelixMalfait marked this conversation as resolved.
Show resolved Hide resolved
fieldMetadataId: string;
value: string;
displayValue: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useCallback } from 'react';
import { v4 } from 'uuid';

import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { isDefined } from '~/utils/isDefined';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';

type UseHandleToggleTrashColumnFilterProps = {
objectNameSingular: string;
viewBarId: string;
};

export const useHandleToggleTrashColumnFilter = ({
viewBarId,
objectNameSingular,
}: UseHandleToggleTrashColumnFilterProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});

const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);

const { upsertCombinedViewFilter } = useCombinedViewFilters(viewBarId);

const handleToggleTrashColumnFilter = useCallback(() => {
const trashFieldMetadata = objectMetadataItem.fields.find(
(field) => field.name === 'deletedAt',
);

if (!isDefined(trashFieldMetadata)) return;

const correspondingColumnDefinition = columnDefinitions.find(
(columnDefinition) =>
columnDefinition.fieldMetadataId === trashFieldMetadata.id,
);

if (!isDefined(correspondingColumnDefinition)) return;

const filterType = getFilterTypeFromFieldType(
correspondingColumnDefinition?.type,
);

const newFilter: Filter = {
id: v4(),
variant: 'trash',
fieldMetadataId: trashFieldMetadata.id,
operand: ViewFilterOperand.IsNotEmpty,
displayValue: '',
definition: {
label: 'Trash',
iconName: 'IconTrash',
fieldMetadataId: trashFieldMetadata.id,
type: filterType,
},
value: '',
};

upsertCombinedViewFilter(newFilter);
}, [columnDefinitions, objectMetadataItem.fields, upsertCombinedViewFilter]);

return handleToggleTrashColumnFilter;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IconFileImport,
IconSettings,
IconTag,
IconTrash,
} from 'twenty-ui';

import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular';
Expand Down Expand Up @@ -37,6 +38,7 @@ import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { ViewType } from '@/views/types/ViewType';
import { useLocation } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';

type RecordIndexOptionsMenu = 'fields' | 'hiddenFields';

Expand Down Expand Up @@ -88,6 +90,11 @@ export const RecordIndexOptionsDropdownContent = ({
hiddenTableColumns,
} = useRecordIndexOptionsForTable(recordIndexId);

const handleToggleTrashColumnFilter = useHandleToggleTrashColumnFilter({
objectNameSingular,
viewBarId: recordIndexId,
});

const {
visibleBoardFields,
hiddenBoardFields,
Expand Down Expand Up @@ -153,6 +160,14 @@ export const RecordIndexOptionsDropdownContent = ({
LeftIcon={IconFileExport}
text={displayedExportProgress(progress)}
/>
<MenuItem
onClick={() => {
handleToggleTrashColumnFilter();
closeDropdown();
}}
LeftIcon={IconTrash}
text="Trash"
/>
</DropdownMenuItemsContainer>
)}
{currentMenu === 'fields' && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent, IconX } from 'twenty-ui';

const StyledChip = styled.div`
const StyledChip = styled.div<{ variant: SortOrFitlerChipVariant }>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: SortOrFitlerChipVariant is misspelled. Should be SortOrFilterChipVariant

align-items: center;
background-color: ${({ theme }) => theme.accent.quaternary};
border: 1px solid ${({ theme }) => theme.accent.tertiary};
background-color: ${({ theme, variant }) => {
switch (variant) {
case 'delete':
return theme.background.danger;
case 'default':
default:
return theme.accent.quaternary;
}
}};
border: 1px solid
${({ theme, variant }) => {
switch (variant) {
case 'delete':
return theme.border.color.danger;
case 'default':
default:
return theme.accent.tertiary;
}
}};
border-radius: 4px;
color: ${({ theme }) => theme.color.blue};
color: ${({ theme, variant }) => {
switch (variant) {
case 'delete':
return theme.color.red;
case 'default':
default:
return theme.color.blue;
}
}};
cursor: pointer;
display: flex;
flex-direction: row;
Expand All @@ -24,7 +49,7 @@ const StyledIcon = styled.div`
margin-right: ${({ theme }) => theme.spacing(1)};
`;

const StyledDelete = styled.div`
const StyledDelete = styled.div<{ variant: SortOrFitlerChipVariant }>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: SortOrFitlerChipVariant is misspelled. Should be SortOrFilterChipVariant

align-items: center;
cursor: pointer;
display: flex;
Expand All @@ -33,7 +58,15 @@ const StyledDelete = styled.div`
margin-top: 1px;
user-select: none;
&:hover {
background-color: ${({ theme }) => theme.accent.secondary};
background-color: ${({ theme, variant }) => {
switch (variant) {
case 'delete':
FelixMalfait marked this conversation as resolved.
Show resolved Hide resolved
return theme.color.red20;
case 'default':
default:
return theme.accent.secondary;
}
}};
border-radius: ${({ theme }) => theme.border.radius.sm};
}
`;
Expand All @@ -42,9 +75,12 @@ const StyledLabelKey = styled.div`
font-weight: ${({ theme }) => theme.font.weight.medium};
`;

type SortOrFitlerChipVariant = 'default' | 'delete';

type SortOrFilterChipProps = {
labelKey?: string;
labelValue: string;
variant?: SortOrFitlerChipVariant;
Icon?: IconComponent;
onRemove: () => void;
onClick?: () => void;
Expand All @@ -54,6 +90,7 @@ type SortOrFilterChipProps = {
export const SortOrFilterChip = ({
labelKey,
labelValue,
variant = 'default',
Icon,
onRemove,
testId,
Expand All @@ -67,7 +104,7 @@ export const SortOrFilterChip = ({
};

return (
<StyledChip onClick={onClick}>
<StyledChip onClick={onClick} variant={variant}>
{Icon && (
<StyledIcon>
<Icon size={theme.icon.size.sm} />
Expand All @@ -76,6 +113,7 @@ export const SortOrFilterChip = ({
{labelKey && <StyledLabelKey>{labelKey}</StyledLabelKey>}
{labelValue}
<StyledDelete
variant={variant}
onClick={handleDeleteClick}
data-testid={'remove-icon-' + testId}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useIcons } from 'twenty-ui';

import { SortOrFilterChip } from '@/views/components/SortOrFilterChip';
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
import { useMemo } from 'react';

type VariantFilterChipProps = {
viewFilter: Filter;
};

export const VariantFilterChip = ({ viewFilter }: VariantFilterChipProps) => {
const { removeCombinedViewFilter } = useCombinedViewFilters();

const { getIcon } = useIcons();

const handleRemoveClick = () => {
removeCombinedViewFilter(viewFilter.id);
};

const variant = useMemo(() => {
switch (viewFilter.variant) {
case 'trash':
FelixMalfait marked this conversation as resolved.
Show resolved Hide resolved
return 'delete';
case 'default':
default:
return 'default';
}
}, [viewFilter.variant]);

return (
<SortOrFilterChip
key={viewFilter.fieldMetadataId}
testId={viewFilter.fieldMetadataId}
variant={variant}
labelValue={viewFilter.definition.label}
Icon={getIcon(viewFilter.definition.iconName)}
onRemove={handleRemoveClick}
/>
);
};
Loading
Loading