Remove sse feature flag (#18114)

As title
This commit is contained in:
Thomas Trompette
2026-02-20 11:46:03 +01:00
committed by GitHub
parent 175df59c21
commit d060c843d1
18 changed files with 5 additions and 288 deletions
@@ -1494,7 +1494,6 @@ export enum FeatureFlagKey {
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
@@ -23,8 +23,8 @@ import {
useSignUpMutation,
useVerifyEmailAndGetLoginTokenMutation,
useVerifyEmailAndGetWorkspaceAgnosticTokenMutation,
type AuthTokenPair,
type AuthToken,
type AuthTokenPair,
} from '~/generated-metadata/graphql';
import { tokenPairState } from '@/auth/states/tokenPairState';
@@ -1,5 +1,5 @@
import { useUpdateFrontComponentApolloCache } from '@/front-components/hooks/useUpdateFrontComponentApolloCache';
import { useListenToMetadataOperationBrowserEvent } from '@/browser-event/hooks/useListenToMetadataOperationBrowserEvent';
import { useUpdateFrontComponentApolloCache } from '@/front-components/hooks/useUpdateFrontComponentApolloCache';
import { useListenToEventsForQuery } from '@/sse-db-event/hooks/useListenToEventsForQuery';
import {
AllMetadataName,
@@ -10,7 +10,6 @@ import { RecordTableAggregateFooter } from '@/object-record/record-table/record-
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { RecordTableVirtualizedDataChangedEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect';
import { RecordTableVirtualizedSSESubscribeEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedSSESubscribeEffect';
import { RecordTableVirtualizedRowTreadmillEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedRowTreadmillEffect';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -1,4 +1,3 @@
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
import { RecordTableCellDragAndDrop } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop';
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
@@ -11,12 +10,8 @@ import { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/reco
import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect';
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
import { ListenRecordUpdatesEffect } from '@/sse-db-event/components/ListenRecordUpdatesEffect';
import { getDefaultRecordFieldsToListen } from '@/sse-db-event/utils/getDefaultRecordFieldsToListen';
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
type RecordTableRowProps = {
recordId: string;
@@ -31,10 +26,6 @@ export const RecordTableRow = ({
rowIndexForDrag,
isFirstRowOfGroup,
}: RecordTableRowProps) => {
const { objectNameSingular } = useRecordIndexContextOrThrow();
const listenedFields = getDefaultRecordFieldsToListen({
objectNameSingular,
});
const isFocused = useRecoilComponentFamilyValue(
isRecordTableRowFocusedComponentFamilyState,
rowIndexForFocus,
@@ -42,9 +33,6 @@ export const RecordTableRow = ({
const isRowFocusActive = useRecoilComponentValue(
isRecordTableRowFocusActiveComponentState,
);
const isSseDbEventsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
);
return isFirstRowOfGroup ? (
<RecordTableDraggableTrFirstRowOfGroup
@@ -63,13 +51,6 @@ export const RecordTableRow = ({
<RecordTableFieldsCells />
<RecordTablePlusButtonCellPlaceholder />
<RecordTableLastEmptyCell />
{!isSseDbEventsEnabled && (
<ListenRecordUpdatesEffect
objectNameSingular={objectNameSingular}
recordId={recordId}
listenedFields={listenedFields}
/>
)}
</RecordTableDraggableTrFirstRowOfGroup>
) : (
<RecordTableDraggableTr
@@ -1,111 +0,0 @@
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useStore } from 'jotai';
import { useOnDbEvent } from '@/sse-db-event/hooks/useOnDbEvent';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { DatabaseEventAction } from '~/generated-metadata/graphql';
type ListenRecordUpdatesEffectProps = {
objectNameSingular: string;
recordId: string;
listenedFields: string[];
};
export const ListenRecordUpdatesEffect = ({
objectNameSingular,
recordId,
listenedFields,
}: ListenRecordUpdatesEffectProps) => {
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular,
});
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { recordGqlFields: computedRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
depth: 1,
objectNameSingular,
});
const store = useStore();
const setRecordInStore = useRecoilCallback(
({ set }) =>
(record: ObjectRecord) => {
set(recordStoreFamilyState(record.id), record);
store.set(recordStoreFamilyStateV2.atomFamily(record.id), record);
},
[store],
);
const { upsertRecordsInStore } = useUpsertRecordsInStore();
useOnDbEvent({
input: { recordId, action: DatabaseEventAction.UPDATED },
onData: (data) => {
const updatedRecord = data.onDbEvent.record;
const cachedRecord = getRecordFromCache<ObjectRecord>(recordId);
const cachedRecordNode = getRecordNodeFromRecord<ObjectRecord>({
record: cachedRecord,
objectMetadataItem,
objectMetadataItems,
computeReferences: false,
});
const shouldHandleOptimisticCache =
isDefined(cachedRecordNode) && isDefined(cachedRecord);
if (shouldHandleOptimisticCache) {
const computedOptimisticRecord: ObjectRecord = {
...cachedRecord,
...updatedRecord,
id: recordId,
__typename: getObjectTypename(objectMetadataItem.nameSingular),
};
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloCoreClient.cache,
record: computedOptimisticRecord,
recordGqlFields: computedRecordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
cache: apolloCoreClient.cache,
objectMetadataItem,
currentRecord: cachedRecordNode,
updatedRecord: updatedRecord,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
setRecordInStore(computedOptimisticRecord);
}
},
skip: listenedFields.length === 0,
});
return null;
};
@@ -7,13 +7,12 @@ import { isCreatingSseEventStreamState } from '@/sse-db-event/states/isCreatingS
import { isDestroyingEventStreamState } from '@/sse-db-event/states/isDestroyingEventStreamState';
import { shouldDestroyEventStreamState } from '@/sse-db-event/states/shouldDestroyEventStreamState';
import { sseEventStreamIdState } from '@/sse-db-event/states/sseEventStreamIdState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isNonEmptyArray } from '@apollo/client/utilities';
import { isNonEmptyString } from '@sniptt/guards';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey, OnboardingStatus } from '~/generated-metadata/graphql';
import { OnboardingStatus } from '~/generated-metadata/graphql';
export const SSEEventStreamEffect = () => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
@@ -28,9 +27,6 @@ export const SSEEventStreamEffect = () => {
const isDestroyingEventStream = useRecoilValue(isDestroyingEventStreamState);
const isLoggedIn = useIsLogged();
const isSseDbEventsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
);
const currentUser = useRecoilValue(currentUserState);
const { triggerEventStreamCreation } = useTriggerEventStreamCreation();
@@ -43,7 +39,6 @@ export const SSEEventStreamEffect = () => {
const willCreateEventStream =
isSseClientAvailable &&
isLoggedIn &&
isSseDbEventsEnabled &&
isDefined(currentUser) &&
currentUser.onboardingStatus === OnboardingStatus.COMPLETED &&
!shouldDestroyEventStream &&
@@ -65,7 +60,6 @@ export const SSEEventStreamEffect = () => {
triggerEventStreamCreation,
isLoggedIn,
currentUser,
isSseDbEventsEnabled,
isDestroyingEventStream,
triggerEventStreamDestroy,
shouldDestroyEventStream,
@@ -1,41 +1,13 @@
import { getTokenPair } from '@/apollo/utils/getTokenPair';
import { SSEClientEffect } from '@/sse-db-event/components/SSEClientEffect';
import { SSEEventStreamEffect } from '@/sse-db-event/components/SSEEventStreamEffect';
import { SSEQuerySubscribeEffect } from '@/sse-db-event/components/SSEQuerySubscribeEffect';
import { SseClientContext } from '@/sse-db-event/contexts/SseClientContext';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { createClient } from 'graphql-sse';
import { type ReactNode } from 'react';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
type SSEProviderProps = {
children: ReactNode;
};
export const SSEProvider = ({ children }: SSEProviderProps) => {
const isSseDbEventsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
);
if (!isSseDbEventsEnabled) {
const tokenPair = getTokenPair();
const token = tokenPair?.accessOrWorkspaceAgnosticToken?.token;
const sseClient = createClient({
url: `${REACT_APP_SERVER_BASE_URL}/metadata`,
headers: {
Authorization: token ? `Bearer ${token}` : '',
},
});
return (
<SseClientContext.Provider value={sseClient}>
{children}
</SseClientContext.Provider>
);
}
return (
<>
<SSEClientEffect />
@@ -1,4 +0,0 @@
import { type Client } from 'graphql-sse';
import { createContext } from 'react';
export const SseClientContext = createContext<Client | null>(null);
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
export const ON_DB_EVENT = gql`
subscription OnDbEvent($input: OnDbEventInput!) {
onDbEvent(input: $input) {
eventDate
action
objectNameSingular
updatedFields
record
}
}
`;
@@ -1,49 +0,0 @@
import { SseClientContext } from '@/sse-db-event/contexts/SseClientContext';
import { ON_DB_EVENT } from '@/sse-db-event/graphql/subscriptions/onDbEvent';
import { useContext, useEffect } from 'react';
import {
type Subscription,
type SubscriptionOnDbEventArgs,
} from '~/generated-metadata/graphql';
type OnDbEventArgs = SubscriptionOnDbEventArgs & {
skip?: boolean;
onData?: (data: Subscription) => void;
onError?: (err: any) => void;
onComplete?: () => void;
};
export const useOnDbEvent = ({
onData,
onError,
onComplete,
input,
skip = false,
}: OnDbEventArgs) => {
const sseClient = useContext(SseClientContext);
useEffect(() => {
if (skip === true) {
return;
}
const next = (value: { data: Subscription }) => onData?.(value.data);
const error = (err: unknown) => onError?.(err);
const complete = () => onComplete?.();
const unsubscribe = sseClient?.subscribe(
{
query: ON_DB_EVENT.loc?.source.body || '',
variables: { input },
},
{
next,
error,
complete,
},
);
return () => {
unsubscribe?.();
};
}, [input, onComplete, onData, onError, skip, sseClient]);
};
@@ -1,14 +0,0 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
export const getDefaultRecordFieldsToListen = ({
objectNameSingular,
}: {
objectNameSingular: string;
}) => {
switch (objectNameSingular) {
case CoreObjectNameSingular.Workflow:
return ['statuses'];
default:
return [];
}
};
@@ -5,14 +5,9 @@ import { WorkflowDiagramEffect } from '@/workflow/workflow-diagram/components/Wo
import { WorkflowSSESubscribeEffect } from '@/workflow/workflow-diagram/components/WorkflowSSESubscribeEffect';
import { WorkflowVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowVisualizerEffect';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const WorkflowCard = () => {
const targetRecord = useTargetRecord();
const isSseDbEventsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
);
return (
<WorkflowVisualizerComponentInstanceContext.Provider
@@ -23,9 +18,7 @@ export const WorkflowCard = () => {
}}
>
<WorkflowVisualizerEffect workflowId={targetRecord.id} />
{isSseDbEventsEnabled && (
<WorkflowSSESubscribeEffect workflowId={targetRecord.id} />
)}
<WorkflowSSESubscribeEffect workflowId={targetRecord.id} />
<WorkflowDiagramEffect />
<WorkflowDiagramCanvasEditable />
</WorkflowVisualizerComponentInstanceContext.Provider>
@@ -1,6 +1,4 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ListenRecordUpdatesEffect } from '@/sse-db-event/components/ListenRecordUpdatesEffect';
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
import { WorkflowRunSSESubscribeEffect } from '@/workflow/workflow-diagram/components/WorkflowRunSSESubscribeEffect';
@@ -8,12 +6,10 @@ import { WorkflowRunVisualizer } from '@/workflow/workflow-diagram/components/Wo
import { WorkflowRunVisualizerEffect } from '@/workflow/workflow-diagram/components/WorkflowRunVisualizerEffect';
import { WorkflowRunVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowRunVisualizerComponentInstanceContext';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Suspense, useId } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledLoadingSkeletonContainer = styled.div`
display: flex;
@@ -45,9 +41,6 @@ const LoadingSkeleton = () => {
export const WorkflowRunCard = () => {
const targetRecord = useTargetRecord();
const componentId = useId();
const isSseDbEventsEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
);
return (
<WorkflowVisualizerComponentInstanceContext.Provider
@@ -63,15 +56,7 @@ export const WorkflowRunCard = () => {
}}
>
<WorkflowRunVisualizerEffect workflowRunId={targetRecord.id} />
{isSseDbEventsEnabled ? (
<WorkflowRunSSESubscribeEffect workflowRunId={targetRecord.id} />
) : (
<ListenRecordUpdatesEffect
objectNameSingular={CoreObjectNameSingular.WorkflowRun}
recordId={targetRecord.id}
listenedFields={['status', 'state']}
/>
)}
<WorkflowRunSSESubscribeEffect workflowRunId={targetRecord.id} />
<Suspense fallback={<LoadingSkeleton />}>
<WorkflowRunVisualizer workflowRunId={targetRecord.id} />
</Suspense>
@@ -29,14 +29,6 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
'https://twenty.com/images/lab/is-row-level-permission-predicates-enabled.png',
},
},
{
key: FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
metadata: {
label: 'Real-Time',
description: 'See all updates without reloading the page',
imagePath: 'https://twenty.com/images/lab/is-real-time-enabled.png',
},
},
...(process.env.CLOUDFLARE_API_KEY
? [
// {
@@ -17,7 +17,6 @@ export enum FeatureFlagKey {
IS_OTHER_FILE_MIGRATED = 'IS_OTHER_FILE_MIGRATED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
IS_NAVIGATION_MENU_ITEM_ENABLED = 'IS_NAVIGATION_MENU_ITEM_ENABLED',
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED = 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED',
@@ -236,7 +236,6 @@ describe('WorkspaceEntityManager', () => {
IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED: false,
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false,
IS_JUNCTION_RELATIONS_ENABLED: false,
IS_SSE_DB_EVENTS_ENABLED: false,
IS_COMMAND_MENU_ITEM_ENABLED: false,
IS_NAVIGATION_MENU_ITEM_ENABLED: false,
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED: false,
@@ -76,11 +76,6 @@ export const seedFeatureFlags = async ({
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_ENABLED,
workspaceId: workspaceId,