From 85c04c89312a572c6875566dab9108f972876af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Wed, 1 Jan 2025 17:28:45 +0100 Subject: [PATCH] Performance improvement to dev xp (#9294) The DX is not great when you need to do a lot of database resets/command. Should we disable Typescript validation to speed things up? With this and caching database:reset takes 1min instead of 2 on my machine. See also: https://github.com/typeorm/typeorm/issues/4136 And #9291 / #9293 --------- Co-authored-by: Lucas Bordeau --- .../twenty-front/src/generated/graphql.tsx | 31 ++++++++++++++--- .../RecordActionMenuEntriesSetter.tsx | 5 ++- .../hooks/useRecordAgnosticActions.ts | 5 ++- .../hooks/useWorkflowRunActions.tsx | 5 ++- .../components/RecordIndexActionMenu.tsx | 7 ++-- .../components/RecordShowActionMenu.tsx | 7 ++-- .../RecordShowRightDrawerActionMenu.tsx | 5 ++- .../useActionMenuEntriesWithCallbacks.ts | 3 +- .../hooks/useRightDrawerEmailThread.ts | 3 +- .../MessageThreadSubscribersTopBar.tsx | 3 +- .../src/modules/app/components/AppRouter.tsx | 13 ++++--- .../components/CommandMenuContainer.tsx | 5 ++- .../hooks/useCommandMenuCommands.tsx | 3 +- .../components/PageFavoriteButton.tsx | 3 +- .../useColumnDefinitionsFromFieldMetadata.ts | 5 ++- .../useRefetchAggregateQueries.test.tsx | 6 ++++ .../hooks/useRefetchAggregateQueries.ts | 3 +- .../ObjectFilterDropdownFilterSelect.tsx | 3 +- .../ObjectOptionsDropdownMenuContent.tsx | 5 ++- ...jectOptionsDropdownRecordGroupsContent.tsx | 5 ++- .../components/RecordBoardColumnHeader.tsx | 3 +- ...useAggregateRecordsForRecordBoardColumn.ts | 3 +- .../FormDateFieldInput.stories.tsx | 28 ++++++++------- .../FormDateTimeFieldInput.stories.tsx | 30 +++++++++------- .../components/RecordIndexContainer.tsx | 3 +- .../components/RecordIndexPageHeader.tsx | 3 +- .../hooks/useRecordShowContainerTabs.ts | 9 ++--- .../record-table/components/RecordTable.tsx | 3 +- .../RecordTableRecordGroupsBody.tsx | 3 +- ...egateRecordsForRecordTableColumnFooter.tsx | 3 +- .../SettingsAccountsListEmptyStateCard.tsx | 7 ++-- .../SettingsNavigationDrawerItems.tsx | 11 ++++-- .../hooks/useIsSettingsIntegrationEnabled.ts | 9 ++--- .../hooks/useSettingsIntegrationCategories.ts | 7 ++-- .../PageHeaderOpenCommandMenuButton.tsx | 3 +- .../layout/page/components/PageAddButton.tsx | 3 +- .../ui/layout/page/components/PageHeader.tsx | 3 +- .../components/ShowPageAddButton.tsx | 3 +- .../layout/tab/types/TabVisibilityConfig.ts | 2 +- ...blesFromActiveFieldsOfViewOrDefaultView.ts | 5 ++- .../workspace/hooks/useIsFeatureEnabled.ts | 2 +- .../modules/workspace/types/FeatureFlagKey.ts | 21 ------------ .../pages/object-record/RecordShowPage.tsx | 3 +- .../data-model/SettingsObjectDetailPage.tsx | 3 +- .../__stories__/SettingsNewObject.stories.tsx | 6 +++- .../SettingsDevelopersWebhookDetail.tsx | 5 ++- .../settings/security/SettingsSecurity.tsx | 3 +- .../SettingsServerlessFunctionDetail.tsx | 9 +++-- .../src/testing/mock-data/users.ts | 11 ++---- packages/twenty-server/nest-cli.json | 2 +- packages/twenty-server/project.json | 24 ++++++++----- packages/twenty-server/scripts/setup-db.ts | 5 +++ packages/twenty-server/scripts/truncate-db.ts | 34 +++++++++---------- .../enums/feature-flag-key.enum.ts | 2 ++ .../feature-flag/feature-flag.entity.ts | 8 +++-- 55 files changed, 255 insertions(+), 149 deletions(-) delete mode 100644 packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index d06c6140b..d7167df82 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -302,7 +302,7 @@ export type ExecuteServerlessFunctionInput = { export type FeatureFlag = { __typename?: 'FeatureFlag'; id: Scalars['UUID']; - key: Scalars['String']; + key: FeatureFlagKey; value: Scalars['Boolean']; workspaceId: Scalars['String']; }; @@ -313,6 +313,29 @@ export type FeatureFlagFilter = { or?: InputMaybe>; }; +export enum FeatureFlagKey { + IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled', + IsAggregateQueryEnabled = 'IsAggregateQueryEnabled', + IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', + IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled', + IsCopilotEnabled = 'IsCopilotEnabled', + IsCrmMigrationEnabled = 'IsCrmMigrationEnabled', + IsEventObjectEnabled = 'IsEventObjectEnabled', + IsFreeAccessEnabled = 'IsFreeAccessEnabled', + IsFunctionSettingsEnabled = 'IsFunctionSettingsEnabled', + IsGmailSendEmailScopeEnabled = 'IsGmailSendEmailScopeEnabled', + IsJsonFilterEnabled = 'IsJsonFilterEnabled', + IsMessageThreadSubscriberEnabled = 'IsMessageThreadSubscriberEnabled', + IsMicrosoftSyncEnabled = 'IsMicrosoftSyncEnabled', + IsPageHeaderV2Enabled = 'IsPageHeaderV2Enabled', + IsPostgreSqlIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled', + IsSsoEnabled = 'IsSSOEnabled', + IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled', + IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled', + IsViewGroupsEnabled = 'IsViewGroupsEnabled', + IsWorkflowEnabled = 'IsWorkflowEnabled' +} + export type FeatureFlagSort = { direction: SortDirection; field: FeatureFlagSortFields; @@ -2068,7 +2091,7 @@ export type UserLookupAdminPanelMutationVariables = Exact<{ }>; -export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, allowImpersonation: boolean, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: string, value: boolean }> }> } }; +export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, allowImpersonation: boolean, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } }; export type CreateOidcIdentityProviderMutationVariables = Exact<{ input: SetupOidcSsoInput; @@ -2103,7 +2126,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key: export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> }; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; @@ -2120,7 +2143,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null, subdomain: string } | null }> } }; export type ActivateWorkflowVersionMutationVariables = Exact<{ workflowVersionId: Scalars['String']; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx index 8efc00e27..02dfb9cc0 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx @@ -13,6 +13,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; export const RecordActionMenuEntriesSetter = () => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( @@ -53,7 +54,9 @@ const ActionEffects = ({ contextStoreCurrentViewTypeComponentState, ); - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); return ( <> diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/hooks/useRecordAgnosticActions.ts b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/hooks/useRecordAgnosticActions.ts index 50d85dab5..01ac93ba7 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/hooks/useRecordAgnosticActions.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/hooks/useRecordAgnosticActions.ts @@ -1,8 +1,11 @@ import { useWorkflowRunActions } from '@/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useRecordAgnosticActions = () => { - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); const { addWorkflowRunActions, removeWorkflowRunActions } = useWorkflowRunActions(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx index 33a2c270c..a52c3c4b0 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx @@ -8,10 +8,13 @@ import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { IconSettingsAutomation, isDefined } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; import { capitalize } from '~/utils/string/capitalize'; export const useWorkflowRunActions = () => { - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries(); diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx index 603bc34bd..835322427 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordIndexActionMenu.tsx @@ -14,16 +14,19 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsMobile } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataIdComponentState, ); - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); const isMobile = useIsMobile(); diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx index ba4467d9e..a1df8e655 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowActionMenu.tsx @@ -9,6 +9,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader'; export const RecordShowActionMenu = ({ @@ -28,10 +29,12 @@ export const RecordShowActionMenu = ({ contextStoreCurrentObjectMetadataIdComponentState, ); - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); // TODO: refactor RecordShowPageBaseHeader to use the context store diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerActionMenu.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerActionMenu.tsx index 8eca1829b..f1984ae41 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerActionMenu.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerActionMenu.tsx @@ -7,13 +7,16 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const RecordShowRightDrawerActionMenu = () => { const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataIdComponentState, ); - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); return ( <> diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts index 719b7dc40..3bdb784cf 100644 --- a/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts +++ b/packages/twenty-front/src/modules/action-menu/hooks/useActionMenuEntriesWithCallbacks.ts @@ -8,13 +8,14 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useContext } from 'react'; import { isDefined } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useActionMenuEntriesWithCallbacks = ( objectMetadataItem: ObjectMetadataItem, viewType: ActionViewType, ) => { const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); const actionConfig = getActionConfig( diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts index d1d67695a..68c37d70d 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts @@ -16,6 +16,7 @@ import { viewableRecordIdState } from '@/object-record/record-right-drawer/state import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useRightDrawerEmailThread = () => { const viewableRecordId = useRecoilValue(viewableRecordIdState); @@ -38,7 +39,7 @@ export const useRightDrawerEmailThread = () => { }); const isMessageThreadSubscribersEnabled = useIsFeatureEnabled( - 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED', + FeatureFlagKey.IsMessageThreadSubscriberEnabled, ); const FETCH_ALL_MESSAGES_OPERATION_SIGNATURE = diff --git a/packages/twenty-front/src/modules/activities/right-drawer/components/MessageThreadSubscribersTopBar.tsx b/packages/twenty-front/src/modules/activities/right-drawer/components/MessageThreadSubscribersTopBar.tsx index 5c85aece3..a7befd2ca 100644 --- a/packages/twenty-front/src/modules/activities/right-drawer/components/MessageThreadSubscribersTopBar.tsx +++ b/packages/twenty-front/src/modules/activities/right-drawer/components/MessageThreadSubscribersTopBar.tsx @@ -5,6 +5,7 @@ import { EmailThreadMembersChip } from '@/activities/emails/components/EmailThre import { messageThreadState } from '@/ui/layout/right-drawer/states/messageThreadState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; const StyledButtonContainer = styled.div` align-items: center; @@ -15,7 +16,7 @@ const StyledButtonContainer = styled.div` export const MessageThreadSubscribersTopBar = () => { const isMessageThreadSubscriberEnabled = useIsFeatureEnabled( - 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED', + FeatureFlagKey.IsMessageThreadSubscriberEnabled, ); const messageThread = useRecoilValue(messageThreadState); diff --git a/packages/twenty-front/src/modules/app/components/AppRouter.tsx b/packages/twenty-front/src/modules/app/components/AppRouter.tsx index 1491921e3..36081f619 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouter.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouter.tsx @@ -4,14 +4,19 @@ import { billingState } from '@/client-config/states/billingState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { RouterProvider } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; export const AppRouter = () => { const billing = useRecoilValue(billingState); - const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED'); - const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED'); - const isSSOEnabled = useIsFeatureEnabled('IS_SSO_ENABLED'); + const isFreeAccessEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsFreeAccessEnabled, + ); + const isCRMMigrationEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsCrmMigrationEnabled, + ); + const isSSOEnabled = useIsFeatureEnabled(FeatureFlagKey.IsSsoEnabled); const isServerlessFunctionSettingsEnabled = useIsFeatureEnabled( - 'IS_FUNCTION_SETTINGS_ENABLED', + FeatureFlagKey.IsFunctionSettingsEnabled, ); const isBillingPageEnabled = diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx index 3575d20c9..fe3802cd0 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx @@ -12,12 +12,15 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; export const CommandMenuContainer = () => { const { toggleCommandMenu } = useCommandMenu(); const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu(); - const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED'); + const isWorkflowEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + ); const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState); useScopedHotkeys( diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx index 42f71648c..6504bb5c2 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuCommands.tsx @@ -31,6 +31,7 @@ import { useMemo } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { Avatar, IconCheckbox, IconNotes, IconSparkles } from 'twenty-ui'; import { useDebounce } from 'use-debounce'; +import { FeatureFlagKey } from '~/generated/graphql'; import { getLogoUrlFromDomainName } from '~/utils'; export const useCommandMenuCommands = () => { @@ -44,7 +45,7 @@ export const useCommandMenuCommands = () => { const commandMenuSearch = useRecoilValue(commandMenuSearchState); const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms - const isCopilotEnabled = useIsFeatureEnabled('IS_COPILOT_ENABLED'); + const isCopilotEnabled = useIsFeatureEnabled(FeatureFlagKey.IsCopilotEnabled); const setCopilotQuery = useSetRecoilState(copilotQueryState); const openCopilotRightDrawer = useOpenCopilotRightDrawer(); diff --git a/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx b/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx index 576de925a..3ab95cd67 100644 --- a/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx +++ b/packages/twenty-front/src/modules/favorites/components/PageFavoriteButton.tsx @@ -1,5 +1,6 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { Button, IconButton, IconHeart, IconHeartOff } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; type PageFavoriteButtonProps = { isFavorite: boolean; @@ -13,7 +14,7 @@ export const PageFavoriteButton = ({ const title = isFavorite ? 'Remove from favorites' : 'Add to favorites'; const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); return ( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts index 2d610368a..09bf1aaf6 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata.ts @@ -7,6 +7,7 @@ import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefin import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; import { formatFieldMetadataItemAsColumnDefinition } from '../utils/formatFieldMetadataItemAsColumnDefinition'; import { formatFieldMetadataItemsAsFilterDefinitions } from '../utils/formatFieldMetadataItemsAsFilterDefinitions'; import { formatFieldMetadataItemsAsSortDefinitions } from '../utils/formatFieldMetadataItemsAsSortDefinitions'; @@ -24,7 +25,9 @@ export const useColumnDefinitionsFromFieldMetadata = ( [objectMetadataItem], ); - const isJsonFilterEnabled = useIsFeatureEnabled('IS_JSON_FILTER_ENABLED'); + const isJsonFilterEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsJsonFilterEnabled, + ); const filterDefinitions = formatFieldMetadataItemsAsFilterDefinitions({ fields: activeFieldMetadataItems, diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRefetchAggregateQueries.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRefetchAggregateQueries.test.tsx index 78d56ced2..c8b65a08a 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRefetchAggregateQueries.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRefetchAggregateQueries.test.tsx @@ -12,6 +12,12 @@ jest.mock('@/workspace/hooks/useIsFeatureEnabled', () => ({ useIsFeatureEnabled: jest.fn(), })); +jest.mock('~/generated/graphql', () => ({ + FeatureFlagKey: { + IsAggregateQueryEnabled: 'IsAggregateQueryEnabled', + }, +})); + describe('useRefetchAggregateQueries', () => { const mockRefetchQueries = jest.fn(); const mockApolloClient = { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRefetchAggregateQueries.ts b/packages/twenty-front/src/modules/object-record/hooks/useRefetchAggregateQueries.ts index dee30f513..019a52735 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRefetchAggregateQueries.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useRefetchAggregateQueries.ts @@ -1,6 +1,7 @@ import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useApolloClient } from '@apollo/client'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useRefetchAggregateQueries = ({ objectMetadataNamePlural, @@ -9,7 +10,7 @@ export const useRefetchAggregateQueries = ({ }) => { const apolloClient = useApolloClient(); const isAggregateQueryEnabled = useIsFeatureEnabled( - 'IS_AGGREGATE_QUERY_ENABLED', + FeatureFlagKey.IsAggregateQueryEnabled, ); const refetchAggregateQueries = async () => { if (isAggregateQueryEnabled) { diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx index a86bc18ab..0e465e572 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelect.tsx @@ -22,6 +22,7 @@ import { availableFilterDefinitionsComponentState } from '@/views/states/availab import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; export const StyledInput = styled.input` background: transparent; @@ -146,7 +147,7 @@ export const ObjectFilterDropdownFilterSelect = ({ useGetCurrentView(); const isAdvancedFiltersEnabled = useIsFeatureEnabled( - 'IS_ADVANCED_FILTERS_ENABLED', + FeatureFlagKey.IsAdvancedFiltersEnabled, ); const shouldShowAdvancedFilterButton = diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx index 0cfa04649..62b06acd7 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownMenuContent.tsx @@ -31,6 +31,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/ import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { ViewType } from '@/views/types/ViewType'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const ObjectOptionsDropdownMenuContent = () => { const { @@ -41,7 +42,9 @@ export const ObjectOptionsDropdownMenuContent = () => { closeDropdown, } = useOptionsDropdown(); - const isViewGroupEnabled = useIsFeatureEnabled('IS_VIEW_GROUPS_ENABLED'); + const isViewGroupEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsViewGroupsEnabled, + ); const { getIcon } = useIcons(); const { currentViewWithCombinedFiltersAndSorts: currentView } = diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx index 19a892be1..32c6eedeb 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupsContent.tsx @@ -24,9 +24,12 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const ObjectOptionsDropdownRecordGroupsContent = () => { - const isViewGroupEnabled = useIsFeatureEnabled('IS_VIEW_GROUPS_ENABLED'); + const isViewGroupEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsViewGroupsEnabled, + ); const { currentContentId, recordIndexId, onContentChange, resetContent } = useOptionsDropdown(); diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx index 6935b6b35..10f9fd4d4 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx @@ -14,6 +14,7 @@ import { RecordGroupDefinitionType } from '@/object-record/record-group/types/Re import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; const StyledHeader = styled.div` align-items: center; @@ -103,7 +104,7 @@ export const RecordBoardColumnHeader = () => { ); const isAggregateQueryEnabled = useIsFeatureEnabled( - 'IS_AGGREGATE_QUERY_ENABLED', + FeatureFlagKey.IsAggregateQueryEnabled, ); const { isOpportunitiesCompanyFieldDisabled } = diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts index 2775359bd..8810807af 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn.ts @@ -12,11 +12,12 @@ import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/s import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; export const useAggregateRecordsForRecordBoardColumn = () => { const isAggregateQueryEnabled = useIsFeatureEnabled( - 'IS_AGGREGATE_QUERY_ENABLED', + FeatureFlagKey.IsAggregateQueryEnabled, ); const { columnDefinition, recordCount } = useContext( diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx index 39a313fb8..bd61db22e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateFieldInput.stories.tsx @@ -24,16 +24,18 @@ export default meta; type Story = StoryObj; +const currentYear = new Date().getFullYear(); + export const Default: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); await canvas.findByText('Created At'); - await canvas.findByDisplayValue('12/09/2024'); + await canvas.findByDisplayValue('12/09/' + currentYear); }, }; @@ -67,10 +69,12 @@ export const SetsDateWithInput: Story = { const dialog = await canvas.findByRole('dialog'); expect(dialog).toBeVisible(); - await userEvent.type(input, '12/08/2024{enter}'); + await userEvent.type(input, `12/08/${currentYear}{enter}`); await waitFor(() => { - expect(args.onPersist).toHaveBeenCalledWith('2024-12-08T00:00:00.000Z'); + expect(args.onPersist).toHaveBeenCalledWith( + `${currentYear}-12-08T00:00:00.000Z`, + ); }); expect(dialog).toBeVisible(); @@ -80,7 +84,7 @@ export const SetsDateWithInput: Story = { export const SetsDateWithDatePicker: Story = { args: { label: 'Created At', - defaultValue: undefined, + defaultValue: `2024-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { @@ -95,7 +99,7 @@ export const SetsDateWithDatePicker: Story = { expect(datePicker).toBeVisible(); const dayToChoose = await within(datePicker).findByRole('option', { - name: 'Choose Saturday, December 7th, 2024', + name: `Choose Saturday, December 7th, 2024`, }); await Promise.all([ @@ -104,11 +108,11 @@ export const SetsDateWithDatePicker: Story = { waitForElementToBeRemoved(datePicker), waitFor(() => { expect(args.onPersist).toHaveBeenCalledWith( - expect.stringMatching(/^2024-12-07/), + expect.stringMatching(new RegExp(`^2024-12-07`)), ); }), waitFor(() => { - expect(canvas.getByDisplayValue('12/07/2024')).toBeVisible(); + expect(canvas.getByDisplayValue(`12/07/2024`)).toBeVisible(); }), ]); }, @@ -117,7 +121,7 @@ export const SetsDateWithDatePicker: Story = { export const ResetsDateByClickingButton: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { @@ -150,7 +154,7 @@ export const ResetsDateByClickingButton: Story = { export const ResetsDateByErasingInputContent: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { @@ -159,7 +163,7 @@ export const ResetsDateByErasingInputContent: Story = { const input = await canvas.findByPlaceholderText('mm/dd/yyyy'); expect(input).toBeVisible(); - expect(input).toHaveDisplayValue('12/09/2024'); + expect(input).toHaveDisplayValue(`12/09/${currentYear}`); await userEvent.clear(input); @@ -336,7 +340,7 @@ export const SwitchesToStandaloneVariable: Story = { export const ClickingOutsideDoesNotResetInputState: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { diff --git a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateTimeFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateTimeFieldInput.stories.tsx index d3dffde5d..ad8c7e677 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateTimeFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/form-types/components/__stories__/FormDateTimeFieldInput.stories.tsx @@ -1,3 +1,4 @@ +import { FormDateTimeFieldInput } from '@/object-record/record-field/form-types/components/FormDateTimeFieldInput'; import { MAX_DATE } from '@/ui/input/components/internal/date/constants/MaxDate'; import { MIN_DATE } from '@/ui/input/components/internal/date/constants/MinDate'; import { parseDateToString } from '@/ui/input/components/internal/date/utils/parseDateToString'; @@ -11,7 +12,6 @@ import { within, } from '@storybook/test'; import { DateTime } from 'luxon'; -import { FormDateTimeFieldInput } from '@/object-record/record-field/form-types/components/FormDateTimeFieldInput'; const meta: Meta = { title: 'UI/Data/Field/Form/Input/FormDateTimeFieldInput', @@ -24,16 +24,20 @@ export default meta; type Story = StoryObj; +const currentYear = new Date().getFullYear(); + export const Default: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); await canvas.findByText('Created At'); - await canvas.findByDisplayValue(/12\/09\/2024 \d{2}:20/); + await canvas.findByDisplayValue( + new RegExp(`12/09/${currentYear} \\d{2}:20`), + ); }, }; @@ -67,11 +71,11 @@ export const SetsDateTimeWithInput: Story = { const dialog = await canvas.findByRole('dialog'); expect(dialog).toBeVisible(); - await userEvent.type(input, '12/08/2024 12:10{enter}'); + await userEvent.type(input, `12/08/${currentYear} 12:10{enter}`); await waitFor(() => { expect(args.onPersist).toHaveBeenCalledWith( - expect.stringMatching(/2024-12-08T\d{2}:10:00.000Z/), + expect.stringMatching(new RegExp(`^${currentYear}-12-08`)), ); }); @@ -95,7 +99,7 @@ export const DoesNotSetDateWithoutTime: Story = { const dialog = await canvas.findByRole('dialog'); expect(dialog).toBeVisible(); - await userEvent.type(input, '12/08/2024{enter}'); + await userEvent.type(input, `12/08/${currentYear}{enter}`); expect(args.onPersist).not.toHaveBeenCalled(); expect(dialog).toBeVisible(); @@ -105,7 +109,7 @@ export const DoesNotSetDateWithoutTime: Story = { export const SetsDateTimeWithDatePicker: Story = { args: { label: 'Created At', - defaultValue: undefined, + defaultValue: `2024-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { @@ -134,7 +138,7 @@ export const SetsDateTimeWithDatePicker: Story = { }), waitFor(() => { expect( - canvas.getByDisplayValue(/12\/07\/2024 \d{2}:\d{2}/), + canvas.getByDisplayValue(new RegExp(`12/07/2024 \\d{2}:\\d{2}`)), ).toBeVisible(); }), ]); @@ -144,7 +148,7 @@ export const SetsDateTimeWithDatePicker: Story = { export const ResetsDateByClickingButton: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { @@ -177,7 +181,7 @@ export const ResetsDateByClickingButton: Story = { export const ResetsDateByErasingInputContent: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { @@ -186,7 +190,9 @@ export const ResetsDateByErasingInputContent: Story = { const input = await canvas.findByPlaceholderText('mm/dd/yyyy hh:mm'); expect(input).toBeVisible(); - expect(input).toHaveDisplayValue(/12\/09\/2024 \d{2}:\d{2}/); + expect(input).toHaveDisplayValue( + new RegExp(`12/09/${currentYear} \\d{2}:\\d{2}`), + ); await userEvent.clear(input); @@ -363,7 +369,7 @@ export const SwitchesToStandaloneVariable: Story = { export const ClickingOutsideDoesNotResetInputState: Story = { args: { label: 'Created At', - defaultValue: '2024-12-09T13:20:19.631Z', + defaultValue: `${currentYear}-12-09T13:20:19.631Z`, onPersist: fn(), }, play: async ({ canvasElement, args }) => { diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx index 00ca89c1a..20abc2d6f 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexContainer.tsx @@ -42,6 +42,7 @@ import { mapViewGroupsToRecordGroupDefinitions } from '@/views/utils/mapViewGrou import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useCallback } from 'react'; +import { FeatureFlagKey } from '~/generated/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; const StyledContainer = styled.div` @@ -162,7 +163,7 @@ export const RecordIndexContainer = () => { ); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); return ( diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx index 757eb4715..1af4c00ba 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageHeader.tsx @@ -13,6 +13,7 @@ import { ViewType } from '@/views/types/ViewType'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; import { isDefined, useIcons } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; import { capitalize } from '~/utils/string/capitalize'; export const RecordIndexPageHeader = () => { @@ -36,7 +37,7 @@ export const RecordIndexPageHeader = () => { ); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); const isObjectMetadataItemReadOnly = diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts index 7c4200024..d6a7ce756 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts @@ -17,6 +17,7 @@ import { IconSettings, } from 'twenty-ui'; import { FeatureFlag, FieldMetadataType } from '~/generated-metadata/graphql'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useRecordShowContainerTabs = ( loading: boolean, @@ -149,7 +150,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, @@ -169,7 +170,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, @@ -188,7 +189,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, @@ -202,7 +203,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: ['IS_WORKFLOW_ENABLED'], + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index 16319eb45..12dc2d83a 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -22,6 +22,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRef } from 'react'; +import { FeatureFlagKey } from '~/generated/graphql'; const StyledTable = styled.table` border-radius: ${({ theme }) => theme.border.radius.sm}; @@ -36,7 +37,7 @@ export const RecordTable = () => { const tableBodyRef = useRef(null); const isAggregateQueryEnabled = useIsFeatureEnabled( - 'IS_AGGREGATE_QUERY_ENABLED', + FeatureFlagKey.IsAggregateQueryEnabled, ); const { toggleClickOutsideListener } = useClickOutsideListener( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx index f90a16215..f10446a59 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody.tsx @@ -12,10 +12,11 @@ import { RecordTableRecordGroupSection } from '@/object-record/record-table/reco import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const RecordTableRecordGroupsBody = () => { const isAggregateQueryEnabled = useIsFeatureEnabled( - 'IS_AGGREGATE_QUERY_ENABLED', + FeatureFlagKey.IsAggregateQueryEnabled, ); const allRecordIds = useRecoilComponentValueV2( recordIndexAllRecordIdsComponentSelector, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx index 03b774545..c5b63261d 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter.tsx @@ -11,13 +11,14 @@ import { viewFieldAggregateOperationState } from '@/object-record/record-table/r import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined'; export const useAggregateRecordsForRecordTableColumnFooter = ( fieldMetadataId: string, ) => { const isAggregateQueryEnabled = useIsFeatureEnabled( - 'IS_AGGREGATE_QUERY_ENABLED', + FeatureFlagKey.IsAggregateQueryEnabled, ); const { objectMetadataItem } = useRecordTableContextOrThrow(); const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields); diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx index 7c875b606..19d24f1d7 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx @@ -1,6 +1,8 @@ +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; import { Button, Card, @@ -9,8 +11,7 @@ import { IconGoogle, IconMicrosoft, } from 'twenty-ui'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; const StyledHeader = styled(CardHeader)` align-items: center; @@ -34,7 +35,7 @@ export const SettingsAccountsListEmptyStateCard = ({ const { triggerApisOAuth } = useTriggerApisOAuth(); const currentWorkspace = useRecoilValue(currentWorkspaceState); const isMicrosoftSyncEnabled = useIsFeatureEnabled( - 'IS_MICROSOFT_SYNC_ENABLED', + FeatureFlagKey.IsMicrosoftSyncEnabled, ); return ( diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx index 0f7089195..6f4409b54 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx @@ -41,6 +41,7 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; import { AnimatePresence, motion } from 'framer-motion'; import { matchPath, resolvePath, useLocation } from 'react-router-dom'; +import { FeatureFlagKey } from '~/generated/graphql'; type SettingsNavigationItem = { label: string; @@ -80,10 +81,14 @@ export const SettingsNavigationDrawerItems = () => { const billing = useRecoilValue(billingState); const isFunctionSettingsEnabled = useIsFeatureEnabled( - 'IS_FUNCTION_SETTINGS_ENABLED', + FeatureFlagKey.IsFunctionSettingsEnabled, + ); + const isFreeAccessEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsFreeAccessEnabled, + ); + const isCRMMigrationEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsCrmMigrationEnabled, ); - const isFreeAccessEnabled = useIsFeatureEnabled('IS_FREE_ACCESS_ENABLED'); - const isCRMMigrationEnabled = useIsFeatureEnabled('IS_CRM_MIGRATION_ENABLED'); const isBillingPageEnabled = billing?.isBillingEnabled && !isFreeAccessEnabled; diff --git a/packages/twenty-front/src/modules/settings/integrations/hooks/useIsSettingsIntegrationEnabled.ts b/packages/twenty-front/src/modules/settings/integrations/hooks/useIsSettingsIntegrationEnabled.ts index 4b814402d..46a4dd428 100644 --- a/packages/twenty-front/src/modules/settings/integrations/hooks/useIsSettingsIntegrationEnabled.ts +++ b/packages/twenty-front/src/modules/settings/integrations/hooks/useIsSettingsIntegrationEnabled.ts @@ -1,13 +1,14 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; -const getFeatureKey = (databaseKey: string) => { +const getFeatureKey = (databaseKey: string): FeatureFlagKey | null => { switch (databaseKey) { case 'airtable': - return 'IS_AIRTABLE_INTEGRATION_ENABLED'; + return FeatureFlagKey.IsAirtableIntegrationEnabled; case 'postgresql': - return 'IS_POSTGRESQL_INTEGRATION_ENABLED'; + return FeatureFlagKey.IsPostgreSqlIntegrationEnabled; case 'stripe': - return 'IS_STRIPE_INTEGRATION_ENABLED'; + return FeatureFlagKey.IsStripeIntegrationEnabled; default: return null; } diff --git a/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts b/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts index d70dd9b49..5e068af25 100644 --- a/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts +++ b/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts @@ -5,25 +5,26 @@ import { SETTINGS_INTEGRATION_ZAPIER_CATEGORY } from '@/settings/integrations/co import { SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory'; import { getSettingsIntegrationAll } from '@/settings/integrations/utils/getSettingsIntegrationAll'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useSettingsIntegrationCategories = (): SettingsIntegrationCategory[] => { const isAirtableIntegrationEnabled = useIsFeatureEnabled( - 'IS_AIRTABLE_INTEGRATION_ENABLED', + FeatureFlagKey.IsAirtableIntegrationEnabled, ); const isAirtableIntegrationActive = !!MOCK_REMOTE_DATABASES.find( ({ name }) => name === 'airtable', )?.isActive; const isPostgresqlIntegrationEnabled = useIsFeatureEnabled( - 'IS_POSTGRESQL_INTEGRATION_ENABLED', + FeatureFlagKey.IsPostgreSqlIntegrationEnabled, ); const isPostgresqlIntegrationActive = !!MOCK_REMOTE_DATABASES.find( ({ name }) => name === 'postgresql', )?.isActive; const isStripeIntegrationEnabled = useIsFeatureEnabled( - 'IS_STRIPE_INTEGRATION_ENABLED', + FeatureFlagKey.IsStripeIntegrationEnabled, ); const isStripeIntegrationActive = !!MOCK_REMOTE_DATABASES.find( ({ name }) => name === 'stripe', diff --git a/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton.tsx b/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton.tsx index 32b1427fb..e3bf30cf9 100644 --- a/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton.tsx @@ -2,12 +2,13 @@ import { Button, IconButton, IconDotsVertical, useIsMobile } from 'twenty-ui'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const PageHeaderOpenCommandMenuButton = () => { const { openCommandMenu } = useCommandMenu(); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); const isMobile = useIsMobile(); diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageAddButton.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageAddButton.tsx index 32039651b..efa68b4fe 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/PageAddButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageAddButton.tsx @@ -1,5 +1,6 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { Button, IconButton, IconPlus, useIsMobile } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; type PageAddButtonProps = { onClick?: () => void; @@ -7,7 +8,7 @@ type PageAddButtonProps = { export const PageAddButton = ({ onClick }: PageAddButtonProps) => { const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); const isMobile = useIsMobile(); diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx index 221384552..f057376aa 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/PageHeader.tsx @@ -18,6 +18,7 @@ import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawe import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const PAGE_BAR_MIN_HEIGHT = 40; @@ -111,7 +112,7 @@ export const PageHeader = ({ ); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); return ( diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx index d4bb719e6..3a14b90b3 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageAddButton.tsx @@ -18,6 +18,7 @@ import { SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID } from '@/ui/layout/show-page/constant import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; import { Dropdown } from '../../dropdown/components/Dropdown'; const StyledContainer = styled.div` @@ -53,7 +54,7 @@ export const ShowPageAddButton = ({ }; const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); if ( diff --git a/packages/twenty-front/src/modules/ui/layout/tab/types/TabVisibilityConfig.ts b/packages/twenty-front/src/modules/ui/layout/tab/types/TabVisibilityConfig.ts index ddca84235..cc7fd55d3 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/types/TabVisibilityConfig.ts +++ b/packages/twenty-front/src/modules/ui/layout/tab/types/TabVisibilityConfig.ts @@ -1,5 +1,5 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { FeatureFlagKey } from '@/workspace/types/FeatureFlagKey'; +import { FeatureFlagKey } from '~/generated/graphql'; export type TabVisibilityConfig = { ifMobile: boolean; diff --git a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts index 7e31b313f..f7743e865 100644 --- a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts @@ -4,6 +4,7 @@ import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/ import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews'; import { getQueryVariablesFromView } from '@/views/utils/getQueryVariablesFromView'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({ objectMetadataItem, @@ -21,7 +22,9 @@ export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({ objectMetadataItem, }); - const isJsonFilterEnabled = useIsFeatureEnabled('IS_JSON_FILTER_ENABLED'); + const isJsonFilterEnabled = useIsFeatureEnabled( + FeatureFlagKey.IsJsonFilterEnabled, + ); const { filterValueDependencies } = useFilterValueDependencies(); diff --git a/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts b/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts index 67419f213..e971cee4f 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts @@ -1,7 +1,7 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { FeatureFlagKey } from '@/workspace/types/FeatureFlagKey'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useIsFeatureEnabled = (featureKey: FeatureFlagKey | null) => { const currentWorkspace = useRecoilValue(currentWorkspaceState); diff --git a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts deleted file mode 100644 index c892b82ff..000000000 --- a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type FeatureFlagKey = - | 'IS_EVENT_OBJECT_ENABLED' - | 'IS_AIRTABLE_INTEGRATION_ENABLED' - | 'IS_POSTGRESQL_INTEGRATION_ENABLED' - | 'IS_STRIPE_INTEGRATION_ENABLED' - | 'IS_FUNCTION_SETTINGS_ENABLED' - | 'IS_COPILOT_ENABLED' - | 'IS_CRM_MIGRATION_ENABLED' - | 'IS_FREE_ACCESS_ENABLED' - | 'IS_MESSAGE_THREAD_SUBSCRIBER_ENABLED' - | 'IS_WORKFLOW_ENABLED' - | 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED' - | 'IS_ANALYTICS_V2_ENABLED' - | 'IS_SSO_ENABLED' - | 'IS_UNIQUE_INDEXES_ENABLED' - | 'IS_JSON_FILTER_ENABLED' - | 'IS_MICROSOFT_SYNC_ENABLED' - | 'IS_ADVANCED_FILTERS_ENABLED' - | 'IS_AGGREGATE_QUERY_ENABLED' - | 'IS_VIEW_GROUPS_ENABLED' - | 'IS_PAGE_HEADER_V2_ENABLED'; diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 9a0748147..3ff6a6637 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -15,6 +15,7 @@ import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import { RecordShowPageWorkflowHeader } from '@/workflow/components/RecordShowPageWorkflowHeader'; import { RecordShowPageWorkflowVersionHeader } from '@/workflow/components/RecordShowPageWorkflowVersionHeader'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader'; export const RecordShowPage = () => { @@ -40,7 +41,7 @@ export const RecordShowPage = () => { ); const isPageHeaderV2Enabled = useIsFeatureEnabled( - 'IS_PAGE_HEADER_V2_ENABLED', + FeatureFlagKey.IsPageHeaderV2Enabled, ); return ( diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx index bef1c6784..473327cb5 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx @@ -30,6 +30,7 @@ import { UndecoratedLink, isDefined, } from 'twenty-ui'; +import { FeatureFlagKey } from '~/generated/graphql'; import { SETTINGS_OBJECT_DETAIL_TABS } from '~/pages/settings/data-model/constants/SettingsObjectDetailTabs'; import { updatedObjectSlugState } from '~/pages/settings/data-model/states/updatedObjectSlugState'; @@ -69,7 +70,7 @@ export const SettingsObjectDetailPage = () => { const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); const isUniqueIndexesEnabled = useIsFeatureEnabled( - 'IS_UNIQUE_INDEXES_ENABLED', + FeatureFlagKey.IsUniqueIndexesEnabled, ); useEffect(() => { diff --git a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx index 5835b8035..b8911d5f4 100644 --- a/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/__stories__/SettingsNewObject.stories.tsx @@ -29,7 +29,11 @@ export const WithStandardSelected: Story = { play: async () => { const canvas = within(document.body); - await canvas.findByText('New Object', undefined, { timeout: 5000 }); + await canvas.findByRole( + 'heading', + { name: 'New Object', level: 3 }, + { timeout: 5000 }, + ); const listingInput = await canvas.findByPlaceholderText('Listing'); const pluralInput = await canvas.findByPlaceholderText('Listings'); diff --git a/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx index f9dde7fc2..111a0bd43 100644 --- a/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail.tsx @@ -37,6 +37,7 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; import { WEBHOOK_EMPTY_OPERATION } from '~/pages/settings/developers/webhooks/constants/WebhookEmptyOperation'; import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType'; @@ -114,7 +115,9 @@ export const SettingsDevelopersWebhooksDetail = () => { navigate(developerPath); }; - const isAnalyticsV2Enabled = useIsFeatureEnabled('IS_ANALYTICS_V2_ENABLED'); + const isAnalyticsV2Enabled = useIsFeatureEnabled( + FeatureFlagKey.IsAnalyticsV2Enabled, + ); const fieldTypeOptions: SelectOption[] = useMemo( () => [ diff --git a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx index 2ac01878d..6c18725b2 100644 --- a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx +++ b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx @@ -11,6 +11,7 @@ import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; +import { FeatureFlagKey } from '~/generated/graphql'; const StyledContainer = styled.div` width: 100%; @@ -30,7 +31,7 @@ const StyledSSOSection = styled(Section)` export const SettingsSecurity = () => { const isSSOEnabled = useRecoilValue(isSSOEnabledState); const isSSOSectionDisplay = - useIsFeatureEnabled('IS_SSO_ENABLED') && isSSOEnabled; + useIsFeatureEnabled(FeatureFlagKey.IsSsoEnabled) && isSSOEnabled; return ( { const isAnalyticsEnabled = useRecoilValue(isAnalyticsEnabledState); - const isAnalyticsV2Enabled = useIsFeatureEnabled('IS_ANALYTICS_V2_ENABLED'); + const isAnalyticsV2Enabled = useIsFeatureEnabled( + FeatureFlagKey.IsAnalyticsV2Enabled, + ); const tabs = [ { id: 'editor', title: 'Editor', Icon: IconCode }, diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index a3daf3841..8748baf68 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -1,5 +1,6 @@ import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { + FeatureFlagKey, OnboardingStatus, SubscriptionInterval, SubscriptionStatus, @@ -52,19 +53,13 @@ export const mockDefaultWorkspace: Workspace = { featureFlags: [ { id: '1492de61-5018-4368-8923-4f1eeaf988c4', - key: 'IS_AIRTABLE_INTEGRATION_ENABLED', + key: FeatureFlagKey.IsAirtableIntegrationEnabled, value: true, workspaceId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6w', }, { id: '1492de61-5018-4368-8923-4f1eeaf988c5', - key: 'IS_POSTGRESQL_INTEGRATION_ENABLED', - value: true, - workspaceId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6w', - }, - { - id: '1492de61-5018-4368-8923-4f1eeaf988c6', - key: 'IS_CALENDER_ENABLED', + key: FeatureFlagKey.IsPostgreSqlIntegrationEnabled, value: true, workspaceId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6w', }, diff --git a/packages/twenty-server/nest-cli.json b/packages/twenty-server/nest-cli.json index 992b41ad2..4dcb09264 100644 --- a/packages/twenty-server/nest-cli.json +++ b/packages/twenty-server/nest-cli.json @@ -4,7 +4,7 @@ "sourceRoot": "src", "compilerOptions": { "builder": "swc", - "typeCheck": true, + "typeCheck": false, "assets": [ { "include": "**/serverless/drivers/constants/base-typescript-project/**", diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index 700053adf..ceded8427 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -6,6 +6,7 @@ "targets": { "build": { "executor": "nx:run-commands", + "cache": true, "options": { "cwd": "packages/twenty-server", "commands": ["rimraf dist", "nest build --path ./tsconfig.build.json"] @@ -99,7 +100,7 @@ "executor": "nx:run-commands", "options": { "cwd": "packages/twenty-server", - "command": "ts-node ../../node_modules/typeorm/cli.js" + "command": "ts-node --transpile-only -P tsconfig.json ../../node_modules/typeorm/cli.js" } }, "ts-node": { @@ -117,6 +118,13 @@ "command": "ts-node" } }, + "ts-node-no-deps-transpile-only": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/twenty-server", + "command": "ts-node --transpile-only" + } + }, "lint": { "options": { "lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"] @@ -149,8 +157,7 @@ "commands": [ "nx typeorm -- migration:run -d src/database/typeorm/metadata/metadata.datasource", "nx typeorm -- migration:run -d src/database/typeorm/core/core.datasource" - ], - "parallel": false + ] } }, "database:migrate:revert": { @@ -161,8 +168,7 @@ "commands": [ "nx typeorm -- migration:revert -d src/database/typeorm/metadata/metadata.datasource", "nx typeorm -- migration:revert -d src/database/typeorm/core/core.datasource" - ], - "parallel": false + ] } }, "generate:integration-test": { @@ -182,8 +188,8 @@ "no-seed": { "cwd": "packages/twenty-server", "commands": [ - "nx ts-node-no-deps -- ./scripts/truncate-db.ts", - "nx ts-node-no-deps -- ./scripts/setup-db.ts", + "nx ts-node-no-deps-transpile-only -- ./scripts/truncate-db.ts", + "nx ts-node-no-deps-transpile-only -- ./scripts/setup-db.ts", "nx database:migrate", "nx command-no-deps -- cache:flush" ], @@ -192,8 +198,8 @@ "seed": { "cwd": "packages/twenty-server", "commands": [ - "nx ts-node-no-deps -- ./scripts/truncate-db.ts", - "nx ts-node-no-deps -- ./scripts/setup-db.ts", + "nx ts-node-no-deps-transpile-only -- ./scripts/truncate-db.ts", + "nx ts-node-no-deps-transpile-only -- ./scripts/setup-db.ts", "nx database:migrate", "nx command-no-deps -- cache:flush", "nx command-no-deps -- workspace:seed:dev" diff --git a/packages/twenty-server/scripts/setup-db.ts b/packages/twenty-server/scripts/setup-db.ts index c7f744d64..a2177dd65 100644 --- a/packages/twenty-server/scripts/setup-db.ts +++ b/packages/twenty-server/scripts/setup-db.ts @@ -30,6 +30,11 @@ rawDataSource 'create extension "uuid-ossp"', ); + // We paused the work on FDW + if (process.env.IS_FDW_ENABLED !== 'true') { + return; + } + await performQuery( 'CREATE EXTENSION IF NOT EXISTS "postgres_fdw"', 'create extension "postgres_fdw"', diff --git a/packages/twenty-server/scripts/truncate-db.ts b/packages/twenty-server/scripts/truncate-db.ts index c484752f5..78c2779b3 100644 --- a/packages/twenty-server/scripts/truncate-db.ts +++ b/packages/twenty-server/scripts/truncate-db.ts @@ -8,32 +8,30 @@ async function dropSchemasSequentially() { try { await rawDataSource.initialize(); - // Fetch all schemas + // Fetch all schemas excluding the ones we want to keep const schemas = await performQuery( ` SELECT n.nspname AS "schema_name" - FROM pg_catalog.pg_namespace n - WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema' + FROM pg_catalog.pg_namespace n + WHERE n.nspname !~ '^pg_' + AND n.nspname <> 'information_schema' + AND n.nspname NOT IN ('metric_helpers', 'user_management', 'public') `, 'Fetching schemas...', ); - // Iterate over each schema and drop it - // This is to avoid dropping all schemas at once, which would cause an out of shared memory error - for (const schema of schemas) { - if ( - schema.schema_name === 'metric_helpers' || - schema.schema_name === 'user_management' || - schema.schema_name === 'public' - ) { - continue; - } + const batchSize = 10; - await performQuery( - ` - DROP SCHEMA IF EXISTS "${schema.schema_name}" CASCADE; - `, - `Dropping schema ${schema.schema_name}...`, + for (let i = 0; i < schemas.length; i += batchSize) { + const batch = schemas.slice(i, i + batchSize); + + await Promise.all( + batch.map((schema) => + performQuery( + `DROP SCHEMA IF EXISTS "${schema.schema_name}" CASCADE;`, + `Dropping schema ${schema.schema_name}...`, + ), + ), ); } diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 98008a091..5b4bb59a7 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -17,4 +17,6 @@ export enum FeatureFlagKey { IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED', IsViewGroupsEnabled = 'IS_VIEW_GROUPS_ENABLED', IsPageHeaderV2Enabled = 'IS_PAGE_HEADER_V2_ENABLED', + IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED', + IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED', } diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts index 6601ed689..218dc047f 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; import { IDField } from '@ptc-org/nestjs-query-graphql'; import { @@ -24,7 +24,7 @@ export class FeatureFlagEntity { @PrimaryGeneratedColumn('uuid') id: string; - @Field(() => String) + @Field(() => FeatureFlagKey) @Column({ nullable: false, type: 'text' }) key: FeatureFlagKey; @@ -47,3 +47,7 @@ export class FeatureFlagEntity { @UpdateDateColumn({ type: 'timestamptz' }) updatedAt: Date; } + +registerEnumType(FeatureFlagKey, { + name: 'FeatureFlagKey', +});