messaging post migration cleanup (#19365)

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
neo773
2026-04-07 14:26:49 +05:30
committed by GitHub
parent 601dc02ed7
commit b23d2b4e73
32 changed files with 155 additions and 353 deletions
@@ -8,6 +8,7 @@ import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import React from 'react';
import { CalendarChannelSyncStage } from 'twenty-shared/types';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCalenderContainer = styled.div`
@@ -20,7 +21,12 @@ export const SettingsAccountsCalendarChannelsContainer = () => {
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID,
);
const { channels: calendarChannels } = useMyCalendarChannels();
const { channels: allCalendarChannels } = useMyCalendarChannels();
const calendarChannels = allCalendarChannels.filter(
(channel) =>
channel.syncStage !== CalendarChannelSyncStage.PENDING_CONFIGURATION,
);
const tabs = [
...calendarChannels.map((calendarChannel) => ({
@@ -1,7 +1,7 @@
import { MessageChannelContactAutoCreationPolicy } from '@/accounts/types/MessageChannel';
import { SettingsAccountsMessageAutoCreationIcon } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationIcon';
import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard';
import { msg } from '@lingui/core/macro';
import { MessageChannelContactAutoCreationPolicy } from 'twenty-shared/types';
type SettingsAccountsMessageAutoCreationCardProps = {
onChange: (nextValue: MessageChannelContactAutoCreationPolicy) => void;
@@ -1,10 +1,6 @@
import { styled } from '@linaria/react';
import {
type MessageChannel,
type MessageChannelContactAutoCreationPolicy,
type MessageFolderImportPolicy,
} from '@/accounts/types/MessageChannel';
import { type MessageChannel } from '@/accounts/types/MessageChannel';
import { UPDATE_MESSAGE_CHANNEL } from '@/settings/accounts/graphql/mutations/updateMessageChannel';
import { useMutation } from '@apollo/client/react';
import { SettingsAccountsMessageAutoCreationCard } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationCard';
@@ -16,6 +12,10 @@ import { H2Title, IconBriefcase, IconUsers } from 'twenty-ui/display';
import { Card, Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type MessageChannelVisibility } from '~/generated/graphql';
import {
type MessageChannelContactAutoCreationPolicy,
type MessageFolderImportPolicy,
} from 'twenty-shared/types';
type SettingsAccountsMessageChannelDetailsProps = {
messageChannel: Pick<
@@ -26,7 +26,6 @@ type SettingsAccountsMessageChannelDetailsProps = {
| 'excludeNonProfessionalEmails'
| 'excludeGroupEmails'
| 'isSyncEnabled'
| 'messageFolders'
| 'messageFolderImportPolicy'
>;
};
@@ -11,6 +11,7 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import React, { useCallback } from 'react';
import { MessageChannelSyncStage } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -27,7 +28,13 @@ export const SettingsAccountsMessageChannelsContainer = () => {
settingsAccountsSelectedMessageChannelState,
);
const { channels: messageChannels } = useMyMessageChannels();
const { channels: allMessageChannels } = useMyMessageChannels();
const messageChannels = allMessageChannels.filter(
(channel) =>
channel.isSyncEnabled &&
channel.syncStage !== MessageChannelSyncStage.PENDING_CONFIGURATION,
);
const tabs = messageChannels.map((messageChannel) => ({
id: messageChannel.id,
@@ -1,8 +1,8 @@
import { MessageFolderImportPolicy } from '@/accounts/types/MessageChannel';
import { SettingsAccountsMessageFoldersCard } from '@/settings/accounts/components/message-folders/SettingsAccountsMessageFoldersCard';
import { SettingsAccountsMessageFolderIcon } from '@/settings/accounts/components/SettingsAccountsMessageFolderIcon';
import { SettingsAccountsRadioSettingsCard } from '@/settings/accounts/components/SettingsAccountsRadioSettingsCard';
import { msg } from '@lingui/core/macro';
import { MessageFolderImportPolicy } from 'twenty-shared/types';
type SettingsAccountsMessageFolderCardProps = {
onChange: (nextValue: MessageFolderImportPolicy) => void;
@@ -1,6 +1,6 @@
import { styled } from '@linaria/react';
import { MessageFolderImportPolicy } from 'twenty-shared/types';
import { MessageFolderImportPolicy } from '@/accounts/types/MessageChannel';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SettingsAccountsMessageFolderIconProps = {
@@ -1,8 +1,11 @@
import { useApolloClient, useMutation } from '@apollo/client/react';
import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { CalendarChannelSyncStage } from '@/accounts/types/CalendarChannel';
import { MessageChannelSyncStage } from '@/accounts/types/MessageChannel';
import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types';
import { useApolloClient, useMutation } from '@apollo/client/react';
import {
CalendarChannelSyncStage,
ConnectedAccountProvider,
MessageChannelSyncStage,
SettingsPath,
} from 'twenty-shared/types';
import { useTriggerProviderReconnect } from '@/settings/accounts/hooks/useTriggerProviderReconnect';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -3,7 +3,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
import {
MessageChannelContactAutoCreationPolicy,
MessageFolderImportPolicy,
} from '@/accounts/types/MessageChannel';
} from 'twenty-shared/types';
import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails';
import { ComponentDecorator } from 'twenty-ui/testing';
import { MessageChannelVisibility } from '~/generated/graphql';
@@ -27,7 +27,6 @@ const meta: Meta<typeof SettingsAccountsMessageChannelDetails> = {
excludeGroupEmails: false,
isSyncEnabled: true,
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
messageFolders: [],
messageFolderImportPolicy: MessageFolderImportPolicy.ALL_FOLDERS,
},
},
@@ -15,6 +15,7 @@ describe('computeFolderIdsForSyncToggle', () => {
externalId?: string | null;
isSynced?: boolean;
}): MessageFolder => ({
__typename: 'MessageFolder',
id,
name,
parentFolderId,
@@ -22,8 +23,8 @@ describe('computeFolderIdsForSyncToggle', () => {
isSentFolder: false,
isSynced,
messageChannelId: 'channel-1',
__typename: 'MessageFolder',
syncCursor: '',
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
});
describe('when syncing a folder', () => {
@@ -8,6 +8,7 @@ describe('computeMessageFolderTree', () => {
parentFolderId: string | null = null,
externalId: string | null = null,
): MessageFolder => ({
__typename: 'MessageFolder',
id,
name,
parentFolderId,
@@ -15,8 +16,8 @@ describe('computeMessageFolderTree', () => {
isSentFolder: false,
isSynced: false,
messageChannelId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
__typename: 'MessageFolder',
syncCursor: '',
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
});
it('should return empty array for empty input', () => {
@@ -11,6 +11,27 @@ export const GET_MY_CONNECTED_ACCOUNTS = gql`
handleAliases
lastSignedInAt
userWorkspaceId
connectionParameters {
IMAP {
host
port
secure
username
password
}
SMTP {
host
port
secure
username
password
}
CALDAV {
host
username
password
}
}
createdAt
updatedAt
}
@@ -1,66 +1,18 @@
import { type CalendarChannel } from '@/accounts/types/CalendarChannel';
import { GET_MY_CALENDAR_CHANNELS } from '@/settings/accounts/graphql/queries/getMyCalendarChannels';
import { useApolloClient, useQuery } from '@apollo/client/react';
import { useMemo } from 'react';
type MetadataCalendarChannel = {
id: string;
handle: string;
visibility: string;
syncStatus: string;
syncStage: string;
syncStageStartedAt: string | null;
isContactAutoCreationEnabled: boolean;
contactAutoCreationPolicy: string;
isSyncEnabled: boolean;
connectedAccountId: string;
createdAt: string;
updatedAt: string;
};
export const useMyCalendarChannels = () => {
const apolloClient = useApolloClient();
const { data: metadataData, loading: metadataLoading } = useQuery<{
myCalendarChannels: MetadataCalendarChannel[];
const { data, loading } = useQuery<{
myCalendarChannels: CalendarChannel[];
}>(GET_MY_CALENDAR_CHANNELS, {
client: apolloClient,
});
const channels = useMemo(() => {
if (!metadataData?.myCalendarChannels) {
return [];
}
return metadataData.myCalendarChannels
.filter(
(channel: MetadataCalendarChannel) =>
channel.syncStage !== 'PENDING_CONFIGURATION',
)
.map(
(channel: MetadataCalendarChannel) =>
({
id: channel.id,
handle: channel.handle,
visibility: channel.visibility,
isContactAutoCreationEnabled: channel.isContactAutoCreationEnabled,
contactAutoCreationPolicy: channel.contactAutoCreationPolicy,
isSyncEnabled: channel.isSyncEnabled,
syncStatus: channel.syncStatus,
syncStage: channel.syncStage,
syncCursor: '',
syncStageStartedAt: channel.syncStageStartedAt
? new Date(channel.syncStageStartedAt)
: null,
throttleFailureCount: 0,
connectedAccountId: channel.connectedAccountId,
__typename: 'CalendarChannel',
}) as CalendarChannel,
);
}, [metadataData]);
return {
channels,
loading: metadataLoading,
channels: data?.myCalendarChannels ?? [],
loading,
};
};
@@ -5,24 +5,16 @@ import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChan
import { useApolloClient, useQuery } from '@apollo/client/react';
import { useMemo } from 'react';
type MetadataConnectedAccount = {
id: string;
handle: string;
provider: string;
authFailedAt: string | null;
scopes: string[] | null;
handleAliases: string[] | null;
lastSignedInAt: string | null;
userWorkspaceId: string;
createdAt: string;
updatedAt: string;
};
type CoreConnectedAccount = Omit<
ConnectedAccount,
'messageChannels' | 'calendarChannels'
>;
export const useMyConnectedAccounts = () => {
const apolloClient = useApolloClient();
const { data: metadataData, loading: metadataLoading } = useQuery<{
myConnectedAccounts: MetadataConnectedAccount[];
const { data, loading: accountsLoading } = useQuery<{
myConnectedAccounts: CoreConnectedAccount[];
}>(GET_MY_CONNECTED_ACCOUNTS, {
client: apolloClient,
});
@@ -33,48 +25,24 @@ export const useMyConnectedAccounts = () => {
useMyCalendarChannels();
const accounts = useMemo<ConnectedAccount[]>(() => {
if (!metadataData?.myConnectedAccounts) {
if (!data?.myConnectedAccounts) {
return [];
}
return metadataData.myConnectedAccounts
.map(
(account: MetadataConnectedAccount) =>
({
id: account.id,
handle: account.handle,
provider: account.provider,
accessToken: '',
refreshToken: '',
accountOwnerId: account.userWorkspaceId,
lastSyncHistoryId: '',
authFailedAt: account.authFailedAt
? new Date(account.authFailedAt)
: null,
messageChannels: messageChannels.filter(
(channel) =>
'connectedAccountId' in channel &&
channel.connectedAccountId === account.id,
),
calendarChannels: calendarChannels.filter(
(channel) =>
'connectedAccountId' in channel &&
channel.connectedAccountId === account.id,
),
scopes: account.scopes,
__typename: 'ConnectedAccount',
}) as ConnectedAccount,
)
.filter(
(account) =>
account.messageChannels.length > 0 ||
account.calendarChannels.length > 0,
);
}, [metadataData, messageChannels, calendarChannels]);
return data.myConnectedAccounts.map((account) => ({
...account,
messageChannels: messageChannels.filter(
(channel) => channel.connectedAccountId === account.id,
),
calendarChannels: calendarChannels.filter(
(channel) => channel.connectedAccountId === account.id,
),
}));
}, [data, messageChannels, calendarChannels]);
return {
accounts,
loading:
metadataLoading || messageChannelsLoading || calendarChannelsLoading,
accountsLoading || messageChannelsLoading || calendarChannelsLoading,
};
};
@@ -1,74 +1,18 @@
import { type MessageChannel } from '@/accounts/types/MessageChannel';
import { GET_MY_MESSAGE_CHANNELS } from '@/settings/accounts/graphql/queries/getMyMessageChannels';
import { useApolloClient, useQuery } from '@apollo/client/react';
import { useMemo } from 'react';
type MetadataMessageChannel = {
id: string;
handle: string;
visibility: string;
type: string;
isContactAutoCreationEnabled: boolean;
contactAutoCreationPolicy: string;
messageFolderImportPolicy: string;
excludeNonProfessionalEmails: boolean;
excludeGroupEmails: boolean;
isSyncEnabled: boolean;
syncStatus: string;
syncStage: string;
syncStageStartedAt: string | null;
connectedAccountId: string;
createdAt: string;
updatedAt: string;
};
export const useMyMessageChannels = () => {
const apolloClient = useApolloClient();
const { data: metadataData, loading: metadataLoading } = useQuery<{
myMessageChannels: MetadataMessageChannel[];
const { data, loading } = useQuery<{
myMessageChannels: MessageChannel[];
}>(GET_MY_MESSAGE_CHANNELS, {
client: apolloClient,
});
const channels = useMemo(() => {
if (!metadataData?.myMessageChannels) {
return [];
}
return metadataData.myMessageChannels
.filter(
(channel: MetadataMessageChannel) =>
channel.isSyncEnabled &&
channel.syncStage !== 'PENDING_CONFIGURATION',
)
.map(
(channel: MetadataMessageChannel) =>
({
id: channel.id,
handle: channel.handle,
visibility: channel.visibility,
contactAutoCreationPolicy: channel.contactAutoCreationPolicy,
excludeNonProfessionalEmails: channel.excludeNonProfessionalEmails,
excludeGroupEmails: channel.excludeGroupEmails,
isSyncEnabled: channel.isSyncEnabled,
messageFolders: [],
messageFolderImportPolicy: channel.messageFolderImportPolicy,
syncStatus: channel.syncStatus,
syncStage: channel.syncStage,
syncCursor: '',
syncStageStartedAt: channel.syncStageStartedAt
? new Date(channel.syncStageStartedAt)
: null,
throttleFailureCount: 0,
connectedAccountId: channel.connectedAccountId,
__typename: 'MessageChannel',
}) as MessageChannel,
);
}, [metadataData]);
return {
channels,
loading: metadataLoading,
channels: data?.myMessageChannels ?? [],
loading,
};
};
@@ -1,52 +1,19 @@
import { type MessageFolder } from '@/accounts/types/MessageFolder';
import { GET_MY_MESSAGE_FOLDERS } from '@/settings/accounts/graphql/queries/getMyMessageFolders';
import { useApolloClient, useQuery } from '@apollo/client/react';
import { useMemo } from 'react';
type MetadataMessageFolder = {
id: string;
name: string | null;
isSynced: boolean;
isSentFolder: boolean;
parentFolderId: string | null;
externalId: string | null;
messageChannelId: string;
createdAt: string;
updatedAt: string;
};
export const useMyMessageFolders = (messageChannelId?: string) => {
const apolloClient = useApolloClient();
const { data: metadataData, loading: metadataLoading } = useQuery<{
myMessageFolders: MetadataMessageFolder[];
const { data, loading } = useQuery<{
myMessageFolders: MessageFolder[];
}>(GET_MY_MESSAGE_FOLDERS, {
client: apolloClient,
variables: messageChannelId ? { messageChannelId } : undefined,
});
const messageFolders = useMemo<MessageFolder[]>(() => {
if (!metadataData?.myMessageFolders) {
return [];
}
return metadataData.myMessageFolders.map(
(folder: MetadataMessageFolder) => ({
id: folder.id,
name: folder.name ?? '',
syncCursor: '',
isSynced: folder.isSynced,
isSentFolder: folder.isSentFolder,
parentFolderId: folder.parentFolderId,
messageChannelId: folder.messageChannelId,
externalId: folder.externalId,
__typename: 'MessageFolder' as const,
}),
);
}, [metadataData]);
return {
messageFolders,
loading: metadataLoading,
messageFolders: data?.myMessageFolders ?? [],
loading,
};
};
@@ -1,13 +1,11 @@
import { SyncStatus } from '@/settings/accounts/constants/SyncStatus';
import { computeSyncStatus } from '@/settings/accounts/utils/computeSyncStatus';
import {
CalendarChannelSyncStage,
CalendarChannelSyncStatus,
} from '@/accounts/types/CalendarChannel';
import {
MessageChannelSyncStage,
MessageChannelSyncStatus,
} from '@/accounts/types/MessageChannel';
import { SyncStatus } from '@/settings/accounts/constants/SyncStatus';
import { computeSyncStatus } from '@/settings/accounts/utils/computeSyncStatus';
} from 'twenty-shared/types';
describe('computeSyncStatus', () => {
test('should return PENDING_CONFIGURATION when message channel sync stage is PENDING_CONFIGURATION', () => {
@@ -1,14 +1,12 @@
import { type CalendarChannel } from '@/accounts/types/CalendarChannel';
import { type MessageChannel } from '@/accounts/types/MessageChannel';
import { SyncStatus } from '@/settings/accounts/constants/SyncStatus';
import {
type CalendarChannel,
CalendarChannelSyncStage,
CalendarChannelSyncStatus,
} from '@/accounts/types/CalendarChannel';
import {
type MessageChannel,
MessageChannelSyncStage,
MessageChannelSyncStatus,
} from '@/accounts/types/MessageChannel';
import { SyncStatus } from '@/settings/accounts/constants/SyncStatus';
} from 'twenty-shared/types';
export const computeSyncStatus = (
messageChannel?: Pick<MessageChannel, 'syncStatus' | 'syncStage'>,