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 all 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
11 changes: 11 additions & 0 deletions packages/twenty-front/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,16 @@ const config: StorybookConfig = {
name: '@storybook/react-vite',
options: {},
},
viteFinal: async (config) => {
// Merge custom configuration into the default config
const { mergeConfig } = await import('vite');

return mergeConfig(config, {
// Add dependencies to pre-optimization
optimizeDeps: {
exclude: ['@tabler/icons-react'],
},
});
},
};
export default config;
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
24 changes: 16 additions & 8 deletions packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Apollo from '@apollo/client';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
Expand Down Expand Up @@ -249,9 +249,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 @@ -344,11 +344,11 @@ export type Mutation = {
generateTransientToken: TransientToken;
impersonate: Verify;
renewToken: AuthTokens;
runWorkflowVersion: WorkflowTriggerResult;
sendInviteLink: SendInviteLink;
signUp: LoginToken;
skipSyncEmailOnboardingStep: OnboardingStepSuccess;
track: Analytics;
triggerWorkflow: WorkflowTriggerResult;
updateBillingSubscription: UpdateBillingEntity;
updateOneObject: Object;
updateOneServerlessFunction: ServerlessFunction;
Expand Down Expand Up @@ -457,6 +457,11 @@ export type MutationRenewTokenArgs = {
};


export type MutationRunWorkflowVersionArgs = {
input: RunWorkflowVersionInput;
};


export type MutationSendInviteLinkArgs = {
emails: Array<Scalars['String']>;
};
Expand All @@ -476,11 +481,6 @@ export type MutationTrackArgs = {
};


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


export type MutationUpdateOneObjectArgs = {
input: UpdateOneObjectInput;
};
Expand Down Expand Up @@ -743,6 +743,13 @@ export enum RemoteTableStatus {
Synced = 'SYNCED'
}

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

export type SendInviteLink = {
__typename?: 'SendInviteLink';
/** Boolean that confirms query was dispatched */
Expand Down Expand Up @@ -1087,6 +1094,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
@@ -1,4 +1,4 @@
import { IconCirclePlus, IconEditCircle, useIcons } from 'twenty-ui';
import { IconCirclePlus, IconEditCircle, IconTrash, useIcons } from 'twenty-ui';

import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
Expand All @@ -19,6 +19,9 @@ export const EventIconDynamicComponent = ({
if (eventAction === 'updated') {
return <IconEditCircle />;
}
if (eventAction === 'deleted') {
return <IconTrash />;
}

const IconComponent = getIcon(linkedObjectMetadataItem?.icon);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ export const EventRowMainObject = ({
/>
);
}
case 'deleted': {
return (
<StyledMainContainer>
<StyledEventRowItemColumn>
{labelIdentifierValue}
</StyledEventRowItemColumn>
<StyledEventRowItemAction>was deleted by</StyledEventRowItemAction>
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
</StyledMainContainer>
);
}
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button } from '@/ui/input/button/components/Button';
import styled from '@emotion/styled';
import { Banner, IconComponent } from 'twenty-ui';
import { Banner, BannerVariant, IconComponent } from 'twenty-ui';

const StyledBanner = styled(Banner)`
position: absolute;
Expand All @@ -14,26 +14,30 @@ const StyledText = styled.div`

export const InformationBanner = ({
message,
variant = 'default',
buttonTitle,
buttonIcon,
buttonOnClick,
}: {
message: string;
buttonTitle: string;
variant?: BannerVariant;
buttonTitle?: string;
buttonIcon?: IconComponent;
buttonOnClick: () => void;
buttonOnClick?: () => void;
}) => {
return (
<StyledBanner>
<StyledBanner variant={variant}>
<StyledText>{message}</StyledText>
<Button
variant="secondary"
title={buttonTitle}
Icon={buttonIcon}
size="small"
inverted
onClick={buttonOnClick}
/>
{buttonTitle && buttonOnClick && (
<Button
variant="secondary"
title={buttonTitle}
Icon={buttonIcon}
size="small"
inverted
onClick={buttonOnClick}
/>
)}
</StyledBanner>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
import styled from '@emotion/styled';
import { IconRefresh } from 'twenty-ui';

const StyledInformationBannerDeletedRecord = styled.div`
height: 40px;
position: relative;

&:empty {
height: 0;
}
`;

export const InformationBannerDeletedRecord = ({
recordId,
objectNameSingular,
}: {
recordId: string;
objectNameSingular: string;
}) => {
const { restoreManyRecords } = useRestoreManyRecords({
objectNameSingular,
});

return (
<StyledInformationBannerDeletedRecord>
<InformationBanner
variant="danger"
message={`This record has been deleted`}
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
buttonTitle="Restore"
buttonIcon={IconRefresh}
buttonOnClick={() => restoreManyRecords([recordId])}
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
/>
</StyledInformationBannerDeletedRecord>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const INFORMATION_BANNER_HEIGHT = '40px';
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { isDefined } from '~/utils/isDefined';
import { sleep } from '~/utils/sleep';
import { capitalize } from '~/utils/string/capitalize';

type useDeleteOneRecordProps = {
type useDeleteManyRecordProps = {
objectNameSingular: string;
refetchFindManyQuery?: boolean;
};
Expand All @@ -25,7 +25,7 @@ type DeleteManyRecordsOptions = {

export const useDeleteManyRecords = ({
objectNameSingular,
}: useDeleteOneRecordProps) => {
}: useDeleteManyRecordProps) => {
const apiConfig = useRecoilValue(apiConfigState);

const mutationPageSize =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import gql from 'graphql-tag';

import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { capitalize } from '~/utils/string/capitalize';

export const useDestroyManyRecordsMutation = ({
objectNameSingular,
}: {
objectNameSingular: string;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});

if (isUndefinedOrNull(objectMetadataItem)) {
return { destroyManyRecordsMutation: EMPTY_MUTATION };
}

const capitalizedObjectName = capitalize(objectMetadataItem.namePlural);

const mutationResponseField = getDestroyManyRecordsMutationResponseField(
objectMetadataItem.namePlural,
);

const destroyManyRecordsMutation = gql`
mutation DestroyMany${capitalizedObjectName}($filter: ${capitalize(
objectMetadataItem.nameSingular,
)}FilterInput!) {
${mutationResponseField}(filter: $filter) {
id
}
}
`;

return {
destroyManyRecordsMutation,
};
};
Loading
Loading