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 <bordeau.lucas@gmail.com>
This commit is contained in:
Félix Malfait
2025-01-01 17:28:45 +01:00
committed by GitHub
parent 3544a49702
commit 85c04c8931
55 changed files with 255 additions and 149 deletions

View File

@ -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 (
<>

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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 (
<>

View File

@ -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(

View File

@ -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 =

View File

@ -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);

View File

@ -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 =

View File

@ -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(

View File

@ -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();

View File

@ -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 (

View File

@ -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,

View File

@ -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 = {

View File

@ -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) {

View File

@ -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 =

View File

@ -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 } =

View File

@ -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();

View File

@ -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 } =

View File

@ -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(

View File

@ -24,16 +24,18 @@ export default meta;
type Story = StoryObj<typeof FormDateFieldInput>;
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 }) => {

View File

@ -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<typeof FormDateTimeFieldInput> = {
title: 'UI/Data/Field/Form/Input/FormDateTimeFieldInput',
@ -24,16 +24,20 @@ export default meta;
type Story = StoryObj<typeof FormDateTimeFieldInput>;
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 }) => {

View File

@ -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 (

View File

@ -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 =

View File

@ -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: [],
},

View File

@ -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<HTMLTableElement>(null);
const isAggregateQueryEnabled = useIsFeatureEnabled(
'IS_AGGREGATE_QUERY_ENABLED',
FeatureFlagKey.IsAggregateQueryEnabled,
);
const { toggleClickOutsideListener } = useClickOutsideListener(

View File

@ -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,

View File

@ -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);

View File

@ -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 (

View File

@ -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;

View File

@ -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;
}

View File

@ -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',

View File

@ -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();

View File

@ -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();

View File

@ -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 (

View File

@ -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 (

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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';