From 04eb9135519ccdf4ce7142b39e75ca6b3e270474 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 14 May 2026 08:12:00 +0200 Subject: [PATCH] chore(page-layout): remove IS_RECORD_PAGE_LAYOUT_* feature flags (#20556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Both \`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED\` and \`IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED\` are force-enabled on every existing workspace by the 1.23.0 upgrade command \`BackfillRecordPageLayoutsCommand\` and seeded enabled for new workspaces via \`DEFAULT_FEATURE_FLAGS\` + \`seed-feature-flags.util.ts\`. They are no longer load-bearing. - Unwrap all \`if (flag) { … }\` conditionals to their enabled branch on both server and front. - Delete legacy fallback files that only the disabled branch reached: \`PageLayoutRelationWidgetsSyncEffect\`, \`usePageLayoutWithRelationWidgets\`, \`reInjectDynamicRelationWidgetsFromDraft\`, \`injectRelationWidgetsIntoLayout\`, \`isDynamicRelationWidget\` (and their tests). - Strip the two \`enableFeatureFlags\` calls from the 1.23 upgrade command — the page-layout backfill data logic itself is kept intact since old workspaces upgrading from < 1.23 still need it. - No DB cleanup migration: stale \`featureFlag\` rows are left in place, matching the precedent set by #20531 and #20460. Net diff: 37 files, +106 / -1727. ## Test plan - [x] \`npx nx typecheck twenty-shared twenty-server twenty-front\` — all pass - [x] \`npx nx lint:diff-with-main twenty-server twenty-front\` — all pass - [x] \`cd packages/twenty-front && npx jest page-layout\` — 1240 tests, all pass - [x] \`cd packages/twenty-server && npx jest workspace-entity-manager.spec\` — pass - [ ] Manual smoke: open a record page, verify tabs render and \"Edit Layout\" command-menu action is available - [ ] Manual smoke: Settings → Data model → object → Layout tab is visible (and hidden for remote / Dashboard objects) - [ ] Manual smoke: edit a tab title, save, reload — confirm persistence --- .../src/metadata/generated/schema.graphql | 2 - .../src/metadata/generated/schema.ts | 4 +- .../src/generated-admin/graphql.ts | 2 - .../src/generated-metadata/graphql.ts | 2 - .../hooks/useSaveLayoutCustomization.ts | 26 +- .../components/PageLayoutContent.tsx | 16 +- .../PageLayoutInitializationQueryEffect.tsx | 66 +--- .../PageLayoutRelationWidgetsSyncEffect.tsx | 193 ---------- .../components/PageLayoutRenderer.tsx | 13 - .../PageLayoutSingleTabRenderer.tsx | 10 - .../components/PageLayoutTabList.tsx | 22 +- ...youtTabListReorderableOverflowDropdown.tsx | 16 +- .../components/PageLayoutTabsRenderer.tsx | 11 +- .../usePageLayoutWithRelationWidgets.test.tsx | 333 ------------------ .../hooks/usePageLayoutAddTabStrategy.ts | 12 +- .../hooks/usePageLayoutWithRelationWidgets.ts | 31 -- .../page-layout/hooks/useSavePageLayout.ts | 24 +- ...onvertPageLayoutDraftToUpdateInput.test.ts | 78 ---- .../injectRelationWidgetsIntoLayout.test.ts | 201 ----------- .../__tests__/isDynamicRelationWidget.test.ts | 43 --- ...ectDynamicRelationWidgetsFromDraft.test.ts | 194 ---------- .../shouldEnableTabEditingFeatures.test.ts | 50 +-- .../convertPageLayoutDraftToUpdateInput.ts | 10 +- .../utils/injectRelationWidgetsIntoLayout.ts | 131 ------- .../utils/isDynamicRelationWidget.ts | 7 - ...reInjectDynamicRelationWidgetsFromDraft.ts | 54 --- .../utils/shouldEnableTabEditingFeatures.ts | 19 +- .../components/RecordPageWidgetRenderer.tsx | 52 +-- .../data-model/SettingsObjectDetailPage.tsx | 4 - ...00-backfill-record-page-layouts.command.ts | 8 +- .../services/field-metadata.service.ts | 15 +- .../object-metadata.service.ts | 145 ++++---- .../workspace-entity-manager.spec.ts | 2 - .../core/utils/seed-feature-flags.util.ts | 10 - .../standard-command-menu-item.constant.ts | 2 +- .../constant/default-feature-flags.ts | 2 - .../twenty-shared/src/types/FeatureFlagKey.ts | 2 - 37 files changed, 107 insertions(+), 1705 deletions(-) delete mode 100644 packages/twenty-front/src/modules/page-layout/components/PageLayoutRelationWidgetsSyncEffect.tsx delete mode 100644 packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx delete mode 100644 packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutWithRelationWidgets.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/__tests__/injectRelationWidgetsIntoLayout.test.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/__tests__/isDynamicRelationWidget.test.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/__tests__/reInjectDynamicRelationWidgetsFromDraft.test.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/injectRelationWidgetsIntoLayout.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/isDynamicRelationWidget.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 25b663bcb8..9976c91dcb 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -1750,12 +1750,10 @@ enum FeatureFlagKey { IS_UNIQUE_INDEXES_ENABLED IS_JSON_FILTER_ENABLED IS_MARKETPLACE_SETTING_TAB_VISIBLE - IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED IS_PUBLIC_DOMAIN_ENABLED IS_EMAILING_DOMAIN_ENABLED IS_EMAIL_GROUP_ENABLED IS_JUNCTION_RELATIONS_ENABLED - IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED IS_REST_METADATA_API_NEW_FORMAT_DIRECT } diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 31647dd03d..8b50d21c0e 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1391,7 +1391,7 @@ export interface FeatureFlag { __typename: 'FeatureFlag' } -export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' +export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' export interface WorkspaceUrls { customUrl?: Scalars['String'] @@ -8762,12 +8762,10 @@ export const enumFeatureFlagKey = { IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const, IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const, IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const, - IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const, IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const, IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const, IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const, IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const, - IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const, IS_REST_METADATA_API_NEW_FORMAT_DIRECT: 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' as const } diff --git a/packages/twenty-front/src/generated-admin/graphql.ts b/packages/twenty-front/src/generated-admin/graphql.ts index 24e6ddca53..b750e9c075 100644 --- a/packages/twenty-front/src/generated-admin/graphql.ts +++ b/packages/twenty-front/src/generated-admin/graphql.ts @@ -276,8 +276,6 @@ export enum FeatureFlagKey { IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED', IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE', IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', - IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', - IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED' } diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 6aa6f0d343..9c18146be8 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1647,8 +1647,6 @@ export enum FeatureFlagKey { IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED', IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE', IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', - IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', - IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED' } diff --git a/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts b/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts index 82c4e684ec..5f5cbd73d8 100644 --- a/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts +++ b/packages/twenty-front/src/modules/layout-customization/hooks/useSaveLayoutCustomization.ts @@ -18,15 +18,12 @@ import { type PageLayout } from '@/page-layout/types/PageLayout'; import { convertPageLayoutDraftToUpdateInput } from '@/page-layout/utils/convertPageLayoutDraftToUpdateInput'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; import { isDefaultPageLayoutId } from '@/page-layout/utils/isDefaultPageLayoutId'; -import { reInjectDynamicRelationWidgetsFromDraft } from '@/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft'; import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useLingui } from '@lingui/react/macro'; import { useStore } from 'jotai'; import { useCallback, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { logError } from '~/utils/logError'; @@ -48,10 +45,6 @@ export const useSaveLayoutCustomization = () => { const { exitLayoutCustomizationMode } = useExitLayoutCustomizationMode(); const { savePageLayoutWidgetsData } = useSavePageLayoutWidgetsData(); - const featureFlags = useFeatureFlagsMap(); - const isRecordPageLayoutEditingEnabled = - featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; - const save = useCallback(async () => { setIsSaving(true); try { @@ -119,10 +112,7 @@ export const useSaveLayoutCustomization = () => { await createPendingRecordTableWidgetViews(pageLayoutId); if (isPageLayoutStructureDirty) { - const updateInput = convertPageLayoutDraftToUpdateInput(draft, { - shouldFilterDynamicRelationWidgets: - !isRecordPageLayoutEditingEnabled, - }); + const updateInput = convertPageLayoutDraftToUpdateInput(draft); const result = await updatePageLayoutWithTabsAndWidgets( pageLayoutId, updateInput, @@ -136,26 +126,17 @@ export const useSaveLayoutCustomization = () => { const persistedLayout: PageLayout = transformPageLayout(updatedPageLayout); - const pageLayoutToPersist = - !isRecordPageLayoutEditingEnabled && - persistedLayout.type === PageLayoutType.RECORD_PAGE - ? reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - draft, - ) - : persistedLayout; - store.set( pageLayoutPersistedComponentState.atomFamily({ instanceId: pageLayoutId, }), - pageLayoutToPersist, + persistedLayout, ); store.set( pageLayoutCurrentLayoutsComponentState.atomFamily({ instanceId: pageLayoutId, }), - convertPageLayoutToTabLayouts(pageLayoutToPersist), + convertPageLayoutToTabLayouts(persistedLayout), ); } } else { @@ -194,7 +175,6 @@ export const useSaveLayoutCustomization = () => { savePageLayoutWidgetsData, exitLayoutCustomizationMode, enqueueErrorSnackBar, - isRecordPageLayoutEditingEnabled, store, t, ]); diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx index aa6a5f89d8..41666a0f6c 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutContent.tsx @@ -9,10 +9,8 @@ import { usePageLayoutTabWithVisibleWidgetsOrThrow } from '@/page-layout/hooks/u import { useReorderPageLayoutWidgets } from '@/page-layout/hooks/useReorderPageLayoutWidgets'; import { StandaloneWidgetPlaceholder } from '@/page-layout/widgets/components/StandaloneWidgetPlaceholder'; import { RecordPageAddWidgetSection } from '@/page-layout/widgets/components/RecordPageAddWidgetSection'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { styled } from '@linaria/react'; import { - FeatureFlagKey, PageLayoutTabLayoutMode, PageLayoutType, } from '~/generated-metadata/graphql'; @@ -38,10 +36,6 @@ export const PageLayoutContent = () => { const isRecordPageLayout = currentPageLayout.type === PageLayoutType.RECORD_PAGE; - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - const isCanvasLayout = layoutMode === PageLayoutTabLayoutMode.CANVAS; const isVerticalList = layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST; @@ -62,19 +56,13 @@ export const PageLayoutContent = () => { } if (isVerticalList) { - if ( - isPageLayoutInEditMode && - isRecordPageLayout && - isRecordPageGlobalEditionEnabled - ) { + if (isPageLayoutInEditMode && isRecordPageLayout) { return ( : undefined - } + trailingElement={} /> ); } diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx index 4f389ef2b2..83b4bee807 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutInitializationQueryEffect.tsx @@ -1,5 +1,4 @@ import { useBasePageLayout } from '@/page-layout/hooks/useBasePageLayout'; -import { usePageLayoutWithRelationWidgets } from '@/page-layout/hooks/usePageLayoutWithRelationWidgets'; import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; @@ -8,27 +7,24 @@ import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayo import { type PageLayout } from '@/page-layout/types/PageLayout'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; import { isPageLayoutEmpty } from '@/page-layout/utils/isPageLayoutEmpty'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useStore } from 'jotai'; import { useCallback, useEffect } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql'; +import { PageLayoutType } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; type PageLayoutInitializationQueryEffectProps = { pageLayoutId: string; }; -const PageLayoutInitializationEffect = ({ +// oxlint-disable-next-line twenty/effect-components +export const PageLayoutInitializationQueryEffect = ({ pageLayoutId, - pageLayout, -}: { - pageLayoutId: string; - pageLayout: PageLayout | undefined; -}) => { +}: PageLayoutInitializationQueryEffectProps) => { + const pageLayout = useBasePageLayout(pageLayoutId); + const [pageLayoutIsInitialized, setPageLayoutIsInitialized] = useAtomComponentState(pageLayoutIsInitializedComponentState); @@ -99,53 +95,3 @@ const PageLayoutInitializationEffect = ({ return null; }; - -const PageLayoutInitializationWithRelationWidgets = ({ - pageLayoutId, - basePageLayout, -}: { - pageLayoutId: string; - basePageLayout: PageLayout | undefined; -}) => { - const pageLayout = usePageLayoutWithRelationWidgets(basePageLayout); - - return ( - - ); -}; - -// oxlint-disable-next-line twenty/effect-components -export const PageLayoutInitializationQueryEffect = ({ - pageLayoutId, -}: PageLayoutInitializationQueryEffectProps) => { - const { layoutType } = useLayoutRenderingContext(); - - const featureFlags = useFeatureFlagsMap(); - const isRecordPageLayoutEditingEnabled = - featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; - - const basePageLayout = useBasePageLayout(pageLayoutId); - - const needsRelationWidgets = - layoutType === PageLayoutType.RECORD_PAGE && - !isRecordPageLayoutEditingEnabled; - - if (needsRelationWidgets) { - return ( - - ); - } - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRelationWidgetsSyncEffect.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRelationWidgetsSyncEffect.tsx deleted file mode 100644 index a2ef1bf740..0000000000 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRelationWidgetsSyncEffect.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState'; -import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems'; -import { useBasePageLayout } from '@/page-layout/hooks/useBasePageLayout'; -import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; -import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; -import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState'; -import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; -import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; -import { injectRelationWidgetsIntoLayout } from '@/page-layout/utils/injectRelationWidgetsIntoLayout'; -import { isDynamicRelationWidget } from '@/page-layout/utils/isDynamicRelationWidget'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useStore } from 'jotai'; -import { useCallback, useEffect } from 'react'; -import { isDefined } from 'twenty-shared/utils'; -import { PageLayoutType, WidgetType } from '~/generated-metadata/graphql'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -type PageLayoutRelationWidgetsSyncEffectProps = { - pageLayoutId: string; -}; - -export const PageLayoutRelationWidgetsSyncEffect = ({ - pageLayoutId, -}: PageLayoutRelationWidgetsSyncEffectProps) => { - const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext(); - - const pageLayoutIsInitialized = useAtomComponentStateValue( - pageLayoutIsInitializedComponentState, - ); - - const basePageLayout = useBasePageLayout(pageLayoutId); - - const { boxedRelationFieldMetadataItems } = useFieldListFieldMetadataItems({ - objectNameSingular: targetRecordIdentifier?.targetObjectNameSingular ?? '', - }); - - const pageLayoutPersistedComponentCallbackState = - useAtomComponentStateCallbackState(pageLayoutPersistedComponentState); - - const pageLayoutDraftComponentCallbackState = - useAtomComponentStateCallbackState(pageLayoutDraftComponentState); - - const pageLayoutCurrentLayoutsComponentCallbackState = - useAtomComponentStateCallbackState(pageLayoutCurrentLayoutsComponentState); - - const store = useStore(); - const isLayoutCustomizationModeEnabled = useAtomStateValue( - isLayoutCustomizationModeEnabledState, - ); - - const getDraftWithSyncedDynamicRelationWidgets = useCallback( - ( - currentDraft: DraftPageLayout, - layoutWithRelationWidgets: PageLayout, - ): DraftPageLayout => { - return { - ...currentDraft, - tabs: currentDraft.tabs.map((draftTab) => { - const persistedTab = layoutWithRelationWidgets.tabs.find( - (tab) => tab.id === draftTab.id, - ); - - if (!isDefined(persistedTab)) { - return draftTab; - } - - const dynamicRelationWidgets = persistedTab.widgets.filter( - isDynamicRelationWidget, - ); - - const nonDynamicWidgets = draftTab.widgets.filter( - (widget) => !isDynamicRelationWidget(widget), - ); - - if (dynamicRelationWidgets.length === 0) { - return { - ...draftTab, - widgets: nonDynamicWidgets, - }; - } - - const firstFieldsWidgetIndex = nonDynamicWidgets.findIndex( - (widget) => widget.type === WidgetType.FIELDS, - ); - - if (firstFieldsWidgetIndex === -1) { - return { - ...draftTab, - widgets: [...nonDynamicWidgets, ...dynamicRelationWidgets], - }; - } - - const widgetsBeforeFields = nonDynamicWidgets.slice( - 0, - firstFieldsWidgetIndex + 1, - ); - const widgetsAfterFields = nonDynamicWidgets.slice( - firstFieldsWidgetIndex + 1, - ); - - return { - ...draftTab, - widgets: [ - ...widgetsBeforeFields, - ...dynamicRelationWidgets, - ...widgetsAfterFields, - ], - }; - }), - }; - }, - [], - ); - - const syncPageLayoutWithRelationWidgets = useCallback( - (layout: PageLayout) => { - const currentPersisted = store.get( - pageLayoutPersistedComponentCallbackState, - ); - - if (!isDeeplyEqual(layout, currentPersisted)) { - store.set(pageLayoutPersistedComponentCallbackState, layout); - - const currentDraft = store.get(pageLayoutDraftComponentCallbackState); - - const nextDraft = isLayoutCustomizationModeEnabled - ? getDraftWithSyncedDynamicRelationWidgets(currentDraft, layout) - : { - id: layout.id, - name: layout.name, - type: layout.type, - objectMetadataId: layout.objectMetadataId, - tabs: layout.tabs, - defaultTabToFocusOnMobileAndSidePanelId: - layout.defaultTabToFocusOnMobileAndSidePanelId, - }; - - if (!isDeeplyEqual(nextDraft, currentDraft)) { - store.set(pageLayoutDraftComponentCallbackState, nextDraft); - } - - const tabLayouts = convertPageLayoutToTabLayouts({ - ...layout, - tabs: nextDraft.tabs, - }); - store.set(pageLayoutCurrentLayoutsComponentCallbackState, tabLayouts); - } - }, - [ - getDraftWithSyncedDynamicRelationWidgets, - isLayoutCustomizationModeEnabled, - pageLayoutCurrentLayoutsComponentCallbackState, - pageLayoutDraftComponentCallbackState, - pageLayoutPersistedComponentCallbackState, - store, - ], - ); - - useEffect(() => { - if (!pageLayoutIsInitialized) { - return; - } - - if (!isDefined(basePageLayout)) { - return; - } - - const isRecordPage = layoutType === PageLayoutType.RECORD_PAGE; - if (!isRecordPage) { - return; - } - - const layoutWithRelationWidgets = injectRelationWidgetsIntoLayout( - basePageLayout, - boxedRelationFieldMetadataItems, - ); - - syncPageLayoutWithRelationWidgets(layoutWithRelationWidgets); - }, [ - basePageLayout, - boxedRelationFieldMetadataItems, - pageLayoutIsInitialized, - layoutType, - syncPageLayoutWithRelationWidgets, - ]); - - return null; -}; diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx index 1773606f76..f55f8f2eab 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutRenderer.tsx @@ -1,16 +1,13 @@ import { PageLayoutEditModeProvider } from '@/page-layout/components/PageLayoutEditModeProvider'; import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/PageLayoutInitializationQueryEffect'; import { PageLayoutRecordPageCustomizationSessionRegistrationEffect } from '@/page-layout/components/PageLayoutRecordPageCustomizationSessionRegistrationEffect'; -import { PageLayoutRelationWidgetsSyncEffect } from '@/page-layout/components/PageLayoutRelationWidgetsSyncEffect'; import { PageLayoutRendererContent } from '@/page-layout/components/PageLayoutRendererContent'; import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext'; import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql'; type PageLayoutRendererProps = { pageLayoutId: string; @@ -21,10 +18,6 @@ export const PageLayoutRenderer = ({ }: PageLayoutRendererProps) => { const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext(); - const featureFlags = useFeatureFlagsMap(); - const isRecordPageLayoutEditingEnabled = - featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; - const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ pageLayoutId, layoutType, @@ -48,12 +41,6 @@ export const PageLayoutRenderer = ({ > - {!isRecordPageLayoutEditingEnabled && - layoutType === PageLayoutType.RECORD_PAGE && ( - - )} diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx index a258a44974..f472a147ea 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx @@ -3,7 +3,6 @@ import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent'; import { PageLayoutEditModeProvider } from '@/page-layout/components/PageLayoutEditModeProvider'; import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/PageLayoutInitializationQueryEffect'; import { PageLayoutRecordPageCustomizationSessionRegistrationEffect } from '@/page-layout/components/PageLayoutRecordPageCustomizationSessionRegistrationEffect'; -import { PageLayoutRelationWidgetsSyncEffect } from '@/page-layout/components/PageLayoutRelationWidgetsSyncEffect'; import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow'; import { usePageLayoutTabWithVisibleWidgetsOrThrow } from '@/page-layout/hooks/usePageLayoutTabWithVisibleWidgetsOrThrow'; @@ -16,8 +15,6 @@ import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingC import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; type PageLayoutSingleTabRendererProps = { pageLayoutId: string; @@ -79,10 +76,6 @@ export const PageLayoutSingleTabRenderer = ({ }: PageLayoutSingleTabRendererProps) => { const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext(); - const featureFlags = useFeatureFlagsMap(); - const isRecordPageLayoutEditingEnabled = - featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; - const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({ pageLayoutId, layoutType, @@ -106,9 +99,6 @@ export const PageLayoutSingleTabRenderer = ({ > - {!isRecordPageLayoutEditingEnabled && ( - - )} diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx index 0f96733c0f..7205b5595e 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabList.tsx @@ -46,14 +46,10 @@ import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab-list/components/Ta import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { SidePanelPages } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { - FeatureFlagKey, - type PageLayoutType, -} from '~/generated-metadata/graphql'; +import { type PageLayoutType } from '~/generated-metadata/graphql'; const StyledContainer = styled.div` box-sizing: border-box; @@ -112,10 +108,6 @@ export const PageLayoutTabList = ({ const { getIcon } = useIcons(); const { t } = useLingui(); - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - const tabsWithIcons: SingleTabProps[] = tabs.map((tab) => ({ id: tab.id, title: tab.title, @@ -266,10 +258,7 @@ export const PageLayoutTabList = ({ (tabId: string) => { const shouldOpenSettings = isPageLayoutInEditMode && - shouldEnableTabEditingFeatures( - pageLayoutType, - isRecordPageGlobalEditionEnabled, - ); + shouldEnableTabEditingFeatures(pageLayoutType); if (shouldOpenSettings && activeTabId === tabId) { openTabSettings(tabId); @@ -284,7 +273,6 @@ export const PageLayoutTabList = ({ }, [ isPageLayoutInEditMode, - isRecordPageGlobalEditionEnabled, pageLayoutType, activeTabId, isTabSettingsOpen, @@ -297,10 +285,7 @@ export const PageLayoutTabList = ({ (tabId: string) => { const shouldOpenSettings = isPageLayoutInEditMode && - shouldEnableTabEditingFeatures( - pageLayoutType, - isRecordPageGlobalEditionEnabled, - ); + shouldEnableTabEditingFeatures(pageLayoutType); if (shouldOpenSettings && activeTabId === tabId) { openTabSettings(tabId); @@ -316,7 +301,6 @@ export const PageLayoutTabList = ({ }, [ isPageLayoutInEditMode, - isRecordPageGlobalEditionEnabled, pageLayoutType, activeTabId, isTabSettingsOpen, diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx index 488f5a4f44..05ddbd3eb9 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableOverflowDropdown.tsx @@ -26,14 +26,10 @@ import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useContext } from 'react'; import { SidePanelPages } from 'twenty-shared/types'; import { ThemeContext } from 'twenty-ui/theme-constants'; -import { - FeatureFlagKey, - type PageLayoutType, -} from '~/generated-metadata/graphql'; +import { type PageLayoutType } from '~/generated-metadata/graphql'; const StyledOverflowDropdownListDraggableWrapper = styled.div` cursor: grab; @@ -79,16 +75,8 @@ export const PageLayoutTabListReorderableOverflowDropdown = ({ const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - const shouldShowEditButton = - isPageLayoutInEditMode && - shouldEnableTabEditingFeatures( - pageLayoutType, - isRecordPageGlobalEditionEnabled, - ); + isPageLayoutInEditMode && shouldEnableTabEditingFeatures(pageLayoutType); const isPageLayoutTabDragging = useAtomComponentStateValue( isPageLayoutTabDraggingComponentState, diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx index ae18ac4506..f32d3dea3d 100644 --- a/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutTabsRenderer.tsx @@ -21,11 +21,9 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { styled } from '@linaria/react'; import { isDefined } from 'twenty-shared/utils'; import { useIsMobile } from 'twenty-ui/utilities'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; const StyledContainer = styled.div<{ hasPinnedTab: boolean }>` display: grid; grid-template-columns: ${({ hasPinnedTab }) => @@ -73,10 +71,6 @@ export const PageLayoutTabsRenderer = () => { const isMobile = useIsMobile(); - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - const metadataStore = useAtomFamilyStateValue( metadataStoreState, 'objectMetadataItems', @@ -90,10 +84,7 @@ export const PageLayoutTabsRenderer = () => { const canEnableTabEditing = isPageLayoutInEditMode && - shouldEnableTabEditingFeatures( - currentPageLayout.type, - isRecordPageGlobalEditionEnabled, - ); + shouldEnableTabEditingFeatures(currentPageLayout.type); const tabsWithVisibleWidgets = getTabsWithVisibleWidgets({ tabs: currentPageLayout.tabs, diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx deleted file mode 100644 index c09a99cc4c..0000000000 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems'; -import { usePageLayoutWithRelationWidgets } from '@/page-layout/hooks/usePageLayoutWithRelationWidgets'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { renderHook } from '@testing-library/react'; -import { - AggregateOperations, - BarChartLayout, - GraphOrderBy, - PageLayoutTabLayoutMode, - PageLayoutType, - WidgetConfigurationType, - WidgetType, -} from '~/generated-metadata/graphql'; - -jest.mock('@/ui/layout/contexts/LayoutRenderingContext'); -jest.mock( - '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems', -); - -describe('usePageLayoutWithRelationWidgets', () => { - const mockBasePageLayout: PageLayout = { - __typename: 'PageLayout', - id: 'test-layout', - name: 'Test Layout', - type: PageLayoutType.RECORD_PAGE, - objectMetadataId: 'obj-1', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - tabs: [ - { - __typename: 'PageLayoutTab', - applicationId: '', - id: 'tab-1', - title: 'Fields', - isActive: true, - icon: 'IconList', - position: 100, - layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, - pageLayoutId: 'test-layout', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - widgets: [ - { - __typename: 'PageLayoutWidget', - id: 'widget-fields', - applicationId: '', - pageLayoutTabId: 'tab-1', - title: 'Fields', - isActive: true, - type: WidgetType.FIELDS, - objectMetadataId: null, - gridPosition: { - __typename: 'GridPosition', - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - configuration: { - __typename: 'FieldsConfiguration', - configurationType: WidgetConfigurationType.FIELDS, - viewId: null, - }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - }, - { - __typename: 'PageLayoutWidget', - id: 'widget-notes', - applicationId: '', - pageLayoutTabId: 'tab-1', - title: 'Notes', - isActive: true, - type: WidgetType.NOTES, - objectMetadataId: null, - gridPosition: { - __typename: 'GridPosition', - row: 1, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - configuration: { - __typename: 'NotesConfiguration', - configurationType: WidgetConfigurationType.NOTES, - }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - }, - { - __typename: 'PageLayoutWidget', - id: 'widget-other', - applicationId: '', - pageLayoutTabId: 'tab-1', - title: 'Other', - isActive: true, - type: WidgetType.GRAPH, - objectMetadataId: null, - gridPosition: { - __typename: 'GridPosition', - row: 2, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - configuration: { - __typename: 'BarChartConfiguration', - configurationType: WidgetConfigurationType.BAR_CHART, - layout: BarChartLayout.VERTICAL, - aggregateOperation: AggregateOperations.COUNT, - aggregateFieldMetadataId: 'id', - primaryAxisGroupByFieldMetadataId: 'createdAt', - primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, - displayDataLabel: false, - }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - }, - ], - }, - ], - }; - - const mockRelationFields: FieldMetadataItem[] = [ - { - id: 'field-1', - universalIdentifier: 'field-1', - label: 'Related Companies', - name: 'relatedCompanies', - type: 'RELATION', - isNullable: true, - isActive: true, - isSystem: false, - isCustom: false, - defaultValue: null, - options: null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - fromRelationMetadata: null, - toRelationMetadata: null, - relationDefinition: null, - settings: null, - } as FieldMetadataItem, - { - id: 'field-2', - universalIdentifier: 'field-2', - label: 'Related People', - name: 'relatedPeople', - type: 'RELATION', - isNullable: true, - isActive: true, - isSystem: false, - isCustom: false, - defaultValue: null, - options: null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - fromRelationMetadata: null, - toRelationMetadata: null, - relationDefinition: null, - settings: null, - } as FieldMetadataItem, - ]; - - beforeEach(() => { - (useLayoutRenderingContext as jest.Mock).mockReturnValue({ - targetRecordIdentifier: { - targetObjectNameSingular: 'company', - }, - layoutType: PageLayoutType.RECORD_PAGE, - }); - - (useFieldListFieldMetadataItems as jest.Mock).mockReturnValue({ - boxedRelationFieldMetadataItems: mockRelationFields, - }); - }); - - it('should inject relation widgets after the first FIELDS widget', () => { - const { result } = renderHook(() => - usePageLayoutWithRelationWidgets(mockBasePageLayout), - ); - - const firstTab = result.current?.tabs[0]; - expect(firstTab).toBeDefined(); - - const widgets = firstTab?.widgets || []; - expect(widgets.length).toBe(5); // 1 FIELDS + 2 relation + 1 NOTES + 1 OTHER - - // First widget should be FIELDS - expect(widgets[0].type).toBe(WidgetType.FIELDS); - expect(widgets[0].id).toBe('widget-fields'); - - // Next two should be relation widgets - expect(widgets[1].type).toBe(WidgetType.FIELD); - expect(widgets[1].title).toBe('Related Companies'); - expect(widgets[2].type).toBe(WidgetType.FIELD); - expect(widgets[2].title).toBe('Related People'); - - // Then NOTES widget - expect(widgets[3].type).toBe(WidgetType.NOTES); - expect(widgets[3].id).toBe('widget-notes'); - - // Finally OTHER widget - expect(widgets[4].type).toBe(WidgetType.GRAPH); - expect(widgets[4].id).toBe('widget-other'); - }); - - it('should handle layout with no FIELDS widget by appending to end', () => { - const layoutWithoutFields: PageLayout = { - ...mockBasePageLayout, - tabs: [ - { - ...mockBasePageLayout.tabs[0], - widgets: [ - { - __typename: 'PageLayoutWidget', - id: 'widget-other', - applicationId: '', - pageLayoutTabId: 'tab-1', - title: 'Other', - isActive: true, - type: WidgetType.GRAPH, - objectMetadataId: null, - gridPosition: { - __typename: 'GridPosition', - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - configuration: { - __typename: 'BarChartConfiguration', - configurationType: WidgetConfigurationType.BAR_CHART, - layout: BarChartLayout.VERTICAL, - aggregateOperation: AggregateOperations.COUNT, - aggregateFieldMetadataId: 'id', - primaryAxisGroupByFieldMetadataId: 'createdAt', - primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, - displayDataLabel: false, - }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - }, - ], - }, - ], - }; - - const { result } = renderHook(() => - usePageLayoutWithRelationWidgets(layoutWithoutFields), - ); - - const firstTab = result.current?.tabs[0]; - const widgets = firstTab?.widgets || []; - - expect(widgets.length).toBe(3); // 1 OTHER + 2 relation - expect(widgets[0].type).toBe(WidgetType.GRAPH); - expect(widgets[1].type).toBe(WidgetType.FIELD); - expect(widgets[2].type).toBe(WidgetType.FIELD); - }); - - it('should return unchanged layout when no relation fields exist', () => { - (useFieldListFieldMetadataItems as jest.Mock).mockReturnValue({ - boxedRelationFieldMetadataItems: [], - }); - - const { result } = renderHook(() => - usePageLayoutWithRelationWidgets(mockBasePageLayout), - ); - - expect(result.current).toEqual(mockBasePageLayout); - }); - - it('should return undefined when basePageLayout is undefined', () => { - const { result } = renderHook(() => - usePageLayoutWithRelationWidgets(undefined), - ); - - expect(result.current).toBeUndefined(); - }); - - it('should return unchanged layout when not a record page', () => { - (useLayoutRenderingContext as jest.Mock).mockReturnValue({ - targetRecordIdentifier: { - targetObjectNameSingular: 'company', - }, - layoutType: PageLayoutType.DASHBOARD, - }); - - const { result } = renderHook(() => - usePageLayoutWithRelationWidgets(mockBasePageLayout), - ); - - expect(result.current).toEqual(mockBasePageLayout); - }); - - it('should handle layout without Note widget', () => { - const layoutWithoutNotes: PageLayout = { - ...mockBasePageLayout, - tabs: [ - { - ...mockBasePageLayout.tabs[0], - widgets: [ - mockBasePageLayout.tabs[0].widgets[0], // FIELDS widget - mockBasePageLayout.tabs[0].widgets[2], // OTHER widget - ], - }, - ], - }; - - const { result } = renderHook(() => - usePageLayoutWithRelationWidgets(layoutWithoutNotes), - ); - - const firstTab = result.current?.tabs[0]; - const widgets = firstTab?.widgets || []; - - expect(widgets.length).toBe(4); // 1 FIELDS + 2 relation + 1 OTHER - - expect(widgets[0].type).toBe(WidgetType.FIELDS); - expect(widgets[1].type).toBe(WidgetType.FIELD); - expect(widgets[2].type).toBe(WidgetType.FIELD); - expect(widgets[3].type).toBe(WidgetType.GRAPH); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutAddTabStrategy.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutAddTabStrategy.ts index 0223c2fb15..2ba3242b1e 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutAddTabStrategy.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutAddTabStrategy.ts @@ -8,12 +8,11 @@ import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnable import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { t } from '@lingui/core/macro'; import { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { SidePanelPages } from 'twenty-shared/types'; -import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql'; +import { PageLayoutType } from '~/generated-metadata/graphql'; export const usePageLayoutAddTabStrategy = ({ pageLayoutId, @@ -25,10 +24,6 @@ export const usePageLayoutAddTabStrategy = ({ const { currentPageLayout } = useCurrentPageLayoutOrThrow(); const isPageLayoutInEditMode = useIsPageLayoutInEditMode(); - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - const { createPageLayoutTab } = useCreatePageLayoutTab({ pageLayoutId, tabListInstanceId, @@ -66,10 +61,7 @@ export const usePageLayoutAddTabStrategy = ({ const isEnabled = isPageLayoutInEditMode && - shouldEnableTabEditingFeatures( - currentPageLayout.type, - isRecordPageGlobalEditionEnabled, - ); + shouldEnableTabEditingFeatures(currentPageLayout.type); if (!isEnabled) { return undefined; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutWithRelationWidgets.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutWithRelationWidgets.ts deleted file mode 100644 index 935200b7fd..0000000000 --- a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutWithRelationWidgets.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { injectRelationWidgetsIntoLayout } from '@/page-layout/utils/injectRelationWidgetsIntoLayout'; -import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { isDefined } from 'twenty-shared/utils'; -import { PageLayoutType } from '~/generated-metadata/graphql'; - -export const usePageLayoutWithRelationWidgets = ( - basePageLayout: PageLayout | undefined, -): PageLayout | undefined => { - const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext(); - - const { boxedRelationFieldMetadataItems } = useFieldListFieldMetadataItems({ - objectNameSingular: targetRecordIdentifier?.targetObjectNameSingular ?? '', - }); - - if (!isDefined(basePageLayout)) { - return undefined; - } - - const isRecordPage = layoutType === PageLayoutType.RECORD_PAGE; - - if (!isRecordPage) { - return basePageLayout; - } - - return injectRelationWidgetsIntoLayout( - basePageLayout, - boxedRelationFieldMetadataItems, - ); -}; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts b/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts index 8bfa57eccc..a1a39718ea 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useSavePageLayout.ts @@ -8,15 +8,12 @@ import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayo import { type PageLayout } from '@/page-layout/types/PageLayout'; import { convertPageLayoutDraftToUpdateInput } from '@/page-layout/utils/convertPageLayoutDraftToUpdateInput'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; -import { reInjectDynamicRelationWidgetsFromDraft } from '@/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft'; import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useStore } from 'jotai'; import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql'; export const useSavePageLayout = (pageLayoutIdFromProps: string) => { const pageLayoutId = useAvailableComponentInstanceIdOrThrow( @@ -49,9 +46,6 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => { const { createPendingRecordTableWidgetViews } = useCreatePendingRecordTableWidgetViews(); - const featureFlags = useFeatureFlagsMap(); - const isRecordPageLayoutEditingEnabled = - featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED]; const store = useStore(); const savePageLayout = useCallback(async () => { @@ -59,9 +53,7 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => { await createPendingRecordTableWidgetViews(pageLayoutId); const pageLayoutDraft = store.get(pageLayoutDraftCallbackState); - const updateInput = convertPageLayoutDraftToUpdateInput(pageLayoutDraft, { - shouldFilterDynamicRelationWidgets: !isRecordPageLayoutEditingEnabled, - }); + const updateInput = convertPageLayoutDraftToUpdateInput(pageLayoutDraft); const result = await updatePageLayoutWithTabsAndWidgets( pageLayoutId, @@ -76,19 +68,10 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => { const persistedLayout: PageLayout = transformPageLayout(updatedPageLayout); - const pageLayoutToPersist = - !isRecordPageLayoutEditingEnabled && - persistedLayout.type === PageLayoutType.RECORD_PAGE - ? reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - pageLayoutDraft, - ) - : persistedLayout; - - store.set(pageLayoutPersistedCallbackState, pageLayoutToPersist); + store.set(pageLayoutPersistedCallbackState, persistedLayout); store.set( pageLayoutCurrentLayoutsCallbackState, - convertPageLayoutToTabLayouts(pageLayoutToPersist), + convertPageLayoutToTabLayouts(persistedLayout), ); } } @@ -97,7 +80,6 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => { }, [ createPendingFieldsWidgetViews, createPendingRecordTableWidgetViews, - isRecordPageLayoutEditingEnabled, pageLayoutCurrentLayoutsCallbackState, pageLayoutDraftCallbackState, pageLayoutId, diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutDraftToUpdateInput.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutDraftToUpdateInput.test.ts index bd503ec7ea..7b3ae3c5f5 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutDraftToUpdateInput.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/convertPageLayoutDraftToUpdateInput.test.ts @@ -1,7 +1,6 @@ import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout'; import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { convertPageLayoutDraftToUpdateInput } from '@/page-layout/utils/convertPageLayoutDraftToUpdateInput'; -import { DYNAMIC_RELATION_WIDGET_ID_PREFIX } from '@/page-layout/utils/isDynamicRelationWidget'; import { PageLayoutTabLayoutMode, PageLayoutType, @@ -350,81 +349,4 @@ describe('convertPageLayoutDraftToUpdateInput', () => { columnSpan: 6, }); }); - - describe('shouldFilterDynamicRelationWidgets', () => { - it('should filter out dynamic relation widgets when shouldFilterDynamicRelationWidgets is true', () => { - const regularWidget = makeWidget({ id: 'w1' }); - const dynamicWidget = makeWidget({ - id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}relation-1`, - type: WidgetType.VIEW, - }); - - const draft = makeDraft([ - makeTab('tab-1', [regularWidget, dynamicWidget]), - ]); - - const result = convertPageLayoutDraftToUpdateInput(draft, { - shouldFilterDynamicRelationWidgets: true, - }); - - expect(result.tabs[0].widgets).toHaveLength(1); - expect(result.tabs[0].widgets[0].id).toBe('w1'); - }); - - it('should keep all non-dynamic widgets when shouldFilterDynamicRelationWidgets is true and multiple widget types exist', () => { - const fieldsWidget = makeWidget({ id: 'w1', type: WidgetType.FIELDS }); - const timelineWidget = makeWidget({ - id: 'w2', - type: WidgetType.TIMELINE, - }); - const dynamicWidget = makeWidget({ - id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}rel`, - }); - - const draft = makeDraft([ - makeTab('tab-1', [fieldsWidget, dynamicWidget, timelineWidget]), - ]); - - const result = convertPageLayoutDraftToUpdateInput(draft, { - shouldFilterDynamicRelationWidgets: true, - }); - - expect(result.tabs[0].widgets).toHaveLength(2); - expect(result.tabs[0].widgets.map((w) => w.id)).toEqual(['w1', 'w2']); - }); - - it('should not filter dynamic relation widgets when shouldFilterDynamicRelationWidgets is false', () => { - const regularWidget = makeWidget({ id: 'w1' }); - const dynamicWidget = makeWidget({ - id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}relation-1`, - type: WidgetType.VIEW, - }); - - const draft = makeDraft([ - makeTab('tab-1', [regularWidget, dynamicWidget]), - ]); - - const result = convertPageLayoutDraftToUpdateInput(draft, { - shouldFilterDynamicRelationWidgets: false, - }); - - expect(result.tabs[0].widgets).toHaveLength(2); - }); - - it('should not filter dynamic relation widgets by default', () => { - const regularWidget = makeWidget({ id: 'w1' }); - const dynamicWidget = makeWidget({ - id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}relation-1`, - type: WidgetType.VIEW, - }); - - const draft = makeDraft([ - makeTab('tab-1', [regularWidget, dynamicWidget]), - ]); - - const result = convertPageLayoutDraftToUpdateInput(draft); - - expect(result.tabs[0].widgets).toHaveLength(2); - }); - }); }); diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/injectRelationWidgetsIntoLayout.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/injectRelationWidgetsIntoLayout.test.ts deleted file mode 100644 index c19c5e4507..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/injectRelationWidgetsIntoLayout.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab'; -import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { injectRelationWidgetsIntoLayout } from '@/page-layout/utils/injectRelationWidgetsIntoLayout'; -import { - FieldDisplayMode, - WidgetConfigurationType, - WidgetType, -} from '~/generated-metadata/graphql'; - -const createMockWidget = ( - id: string, - type: WidgetType = WidgetType.FIELDS, -): PageLayoutWidget => - ({ - __typename: 'PageLayoutWidget', - id, - pageLayoutTabId: 'tab-1', - title: `Widget ${id}`, - type, - objectMetadataId: null, - gridPosition: { - __typename: 'GridPosition', - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - position: { - __typename: 'PageLayoutWidgetGridPosition', - layoutMode: 'GRID', - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - configuration: { - __typename: 'FieldsConfiguration', - configurationType: WidgetConfigurationType.FIELDS, - viewId: null, - }, - createdAt: '2024-01-01T00:00:00.000Z', - updatedAt: '2024-01-01T00:00:00.000Z', - deletedAt: null, - }) as PageLayoutWidget; - -const createMockTab = ( - id: string, - widgets: PageLayoutWidget[], -): PageLayoutTab => - ({ - __typename: 'PageLayoutTab', - applicationId: '', - id, - pageLayoutId: 'page-layout-1', - title: `Tab ${id}`, - position: 0, - widgets, - createdAt: '2024-01-01T00:00:00.000Z', - updatedAt: '2024-01-01T00:00:00.000Z', - deletedAt: null, - }) as PageLayoutTab; - -const createMockLayout = (tabs: PageLayoutTab[]): PageLayout => - ({ - __typename: 'PageLayout', - id: 'layout-1', - tabs, - }) as PageLayout; - -const createMockFieldMetadataItem = ( - id: string, - label: string, -): FieldMetadataItem => - ({ - id, - label, - name: label.toLowerCase(), - type: 'RELATION', - }) as FieldMetadataItem; - -describe('injectRelationWidgetsIntoLayout', () => { - it('should return layout unchanged when relation fields array is empty', () => { - const layout = createMockLayout([ - createMockTab('tab-1', [createMockWidget('w1')]), - ]); - - const result = injectRelationWidgetsIntoLayout(layout, []); - - expect(result).toBe(layout); - }); - - it('should return layout unchanged when there are no tabs', () => { - const layout = createMockLayout([]); - const fields = [createMockFieldMetadataItem('f1', 'Company')]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - expect(result).toBe(layout); - }); - - it('should append relation widgets when no FIELDS widget exists', () => { - const otherWidget = createMockWidget('w1', WidgetType.TIMELINE); - const layout = createMockLayout([createMockTab('tab-1', [otherWidget])]); - const fields = [createMockFieldMetadataItem('f1', 'Company')]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - expect(result.tabs[0].widgets).toHaveLength(2); - expect(result.tabs[0].widgets[0].id).toBe('w1'); - expect(result.tabs[0].widgets[1].id).toContain('dynamic-relation-widget-'); - expect(result.tabs[0].widgets[1].id).toContain('f1'); - }); - - it('should inject relation widgets after the first FIELDS widget', () => { - const fieldsWidget = createMockWidget('fields-1', WidgetType.FIELDS); - const timelineWidget = createMockWidget('timeline-1', WidgetType.TIMELINE); - const layout = createMockLayout([ - createMockTab('tab-1', [fieldsWidget, timelineWidget]), - ]); - const fields = [createMockFieldMetadataItem('f1', 'Company')]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - expect(result.tabs[0].widgets).toHaveLength(3); - expect(result.tabs[0].widgets[0].id).toBe('fields-1'); - expect(result.tabs[0].widgets[1].id).toContain('f1'); - expect(result.tabs[0].widgets[2].id).toBe('timeline-1'); - }); - - it('should reposition NOTES widget after relation widgets', () => { - const fieldsWidget = createMockWidget('fields-1', WidgetType.FIELDS); - const notesWidget = createMockWidget('notes-1', WidgetType.NOTES); - const timelineWidget = createMockWidget('timeline-1', WidgetType.TIMELINE); - const layout = createMockLayout([ - createMockTab('tab-1', [fieldsWidget, notesWidget, timelineWidget]), - ]); - const fields = [createMockFieldMetadataItem('f1', 'Company')]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - const widgetIds = result.tabs[0].widgets.map((w) => w.id); - - expect(widgetIds[0]).toBe('fields-1'); - expect(widgetIds[1]).toContain('f1'); - expect(widgetIds[2]).toBe('notes-1'); - expect(widgetIds[3]).toBe('timeline-1'); - }); - - it('should only modify the first tab', () => { - const layout = createMockLayout([ - createMockTab('tab-1', [createMockWidget('fields-1', WidgetType.FIELDS)]), - createMockTab('tab-2', [createMockWidget('fields-2', WidgetType.FIELDS)]), - ]); - const fields = [createMockFieldMetadataItem('f1', 'Company')]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - expect(result.tabs[0].widgets).toHaveLength(2); - expect(result.tabs[1].widgets).toHaveLength(1); - expect(result.tabs[1].widgets[0].id).toBe('fields-2'); - }); - - it('should inject multiple relation widgets in order', () => { - const fieldsWidget = createMockWidget('fields-1', WidgetType.FIELDS); - const layout = createMockLayout([createMockTab('tab-1', [fieldsWidget])]); - const fields = [ - createMockFieldMetadataItem('f1', 'Company'), - createMockFieldMetadataItem('f2', 'Person'), - ]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - expect(result.tabs[0].widgets).toHaveLength(3); - expect(result.tabs[0].widgets[0].id).toBe('fields-1'); - expect(result.tabs[0].widgets[1].id).toContain('f1'); - expect(result.tabs[0].widgets[2].id).toContain('f2'); - }); - - it('should set correct properties on injected relation widgets', () => { - const layout = createMockLayout([ - createMockTab('tab-1', [createMockWidget('fields-1', WidgetType.FIELDS)]), - ]); - const fields = [createMockFieldMetadataItem('f1', 'Company')]; - - const result = injectRelationWidgetsIntoLayout(layout, fields); - - const injectedWidget = result.tabs[0].widgets[1]; - - expect(injectedWidget.type).toBe(WidgetType.FIELD); - expect(injectedWidget.title).toBe('Company'); - expect(injectedWidget.pageLayoutTabId).toBe('tab-1'); - expect(injectedWidget.configuration).toEqual( - expect.objectContaining({ - fieldMetadataId: 'f1', - fieldDisplayMode: FieldDisplayMode.CARD, - }), - ); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/isDynamicRelationWidget.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/isDynamicRelationWidget.test.ts deleted file mode 100644 index 9247f5d535..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/isDynamicRelationWidget.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { - DYNAMIC_RELATION_WIDGET_ID_PREFIX, - isDynamicRelationWidget, -} from '@/page-layout/utils/isDynamicRelationWidget'; - -const makeWidget = (id: string): PageLayoutWidget => - ({ - id, - title: 'Widget', - type: 'FIELDS', - pageLayoutTabId: 'tab-1', - gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 }, - configuration: {}, - }) as PageLayoutWidget; - -describe('isDynamicRelationWidget', () => { - it('should return true for a widget with the dynamic relation prefix', () => { - const widget = makeWidget( - `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}some-relation`, - ); - - expect(isDynamicRelationWidget(widget)).toBe(true); - }); - - it('should return false for a widget without the dynamic relation prefix', () => { - const widget = makeWidget('regular-widget-id'); - - expect(isDynamicRelationWidget(widget)).toBe(false); - }); - - it('should return false for a widget with a similar but different prefix', () => { - const widget = makeWidget('dynamic-relation-other-widget-123'); - - expect(isDynamicRelationWidget(widget)).toBe(false); - }); - - it('should return true for the prefix alone as id', () => { - const widget = makeWidget(DYNAMIC_RELATION_WIDGET_ID_PREFIX); - - expect(isDynamicRelationWidget(widget)).toBe(true); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/reInjectDynamicRelationWidgetsFromDraft.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/reInjectDynamicRelationWidgetsFromDraft.test.ts deleted file mode 100644 index 24ac8657b6..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/reInjectDynamicRelationWidgetsFromDraft.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { DYNAMIC_RELATION_WIDGET_ID_PREFIX } from '@/page-layout/utils/isDynamicRelationWidget'; -import { reInjectDynamicRelationWidgetsFromDraft } from '@/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft'; -import { WidgetType } from '~/generated-metadata/graphql'; - -const makeWidget = ( - overrides: Partial & { id: string }, -): PageLayoutWidget => - ({ - title: 'Widget', - type: WidgetType.FIELDS, - pageLayoutTabId: 'tab-1', - gridPosition: { row: 0, column: 0, rowSpan: 1, columnSpan: 1 }, - configuration: {}, - ...overrides, - }) as PageLayoutWidget; - -const makeDynamicRelationWidget = (suffix: string): PageLayoutWidget => - makeWidget({ - id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}${suffix}`, - type: WidgetType.VIEW, - title: `Dynamic Relation ${suffix}`, - }); - -const makePersistedLayout = (tabs: PageLayout['tabs']): PageLayout => - ({ - id: 'layout-1', - name: 'Layout', - type: 'RECORD_PAGE', - createdAt: '2025-01-01', - updatedAt: '2025-01-01', - tabs, - }) as PageLayout; - -const makeDraftLayout = (tabs: DraftPageLayout['tabs']): DraftPageLayout => - ({ - id: 'layout-1', - name: 'Layout', - type: 'RECORD_PAGE', - tabs, - }) as DraftPageLayout; - -const makeTab = ( - id: string, - widgets: PageLayoutWidget[], -): PageLayout['tabs'][number] => - ({ - id, - title: 'Tab', - position: 0, - pageLayoutId: 'layout-1', - applicationId: 'app-1', - createdAt: '2025-01-01', - updatedAt: '2025-01-01', - widgets, - }) as PageLayout['tabs'][number]; - -describe('reInjectDynamicRelationWidgetsFromDraft', () => { - it('should return persisted layout unchanged when draft has no dynamic widgets', () => { - const persistedLayout = makePersistedLayout([ - makeTab('tab-1', [makeWidget({ id: 'w1' })]), - ]); - const draft = makeDraftLayout([ - makeTab('tab-1', [makeWidget({ id: 'w1' })]), - ]); - - const result = reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - draft, - ); - - expect(result).toBe(persistedLayout); - }); - - it('should inject dynamic widgets after the first FIELDS widget', () => { - const fieldsWidget = makeWidget({ - id: 'fields-1', - type: WidgetType.FIELDS, - }); - const timelineWidget = makeWidget({ - id: 'timeline-1', - type: WidgetType.TIMELINE, - }); - const dynamicWidget = makeDynamicRelationWidget('relation-1'); - - const persistedLayout = makePersistedLayout([ - makeTab('tab-1', [fieldsWidget, timelineWidget]), - ]); - const draft = makeDraftLayout([ - makeTab('tab-1', [fieldsWidget, dynamicWidget, timelineWidget]), - ]); - - const result = reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - draft, - ); - - expect(result.tabs[0].widgets).toHaveLength(3); - expect(result.tabs[0].widgets[0].id).toBe('fields-1'); - expect(result.tabs[0].widgets[1].id).toBe( - `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}relation-1`, - ); - expect(result.tabs[0].widgets[2].id).toBe('timeline-1'); - }); - - it('should append dynamic widgets if no FIELDS widget exists in the tab', () => { - const timelineWidget = makeWidget({ - id: 'timeline-1', - type: WidgetType.TIMELINE, - }); - const dynamicWidget = makeDynamicRelationWidget('relation-1'); - - const persistedLayout = makePersistedLayout([ - makeTab('tab-1', [timelineWidget]), - ]); - const draft = makeDraftLayout([ - makeTab('tab-1', [timelineWidget, dynamicWidget]), - ]); - - const result = reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - draft, - ); - - expect(result.tabs[0].widgets).toHaveLength(2); - expect(result.tabs[0].widgets[0].id).toBe('timeline-1'); - expect(result.tabs[0].widgets[1].id).toBe( - `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}relation-1`, - ); - }); - - it('should handle multiple tabs independently', () => { - const fieldsWidget1 = makeWidget({ - id: 'fields-1', - type: WidgetType.FIELDS, - }); - const fieldsWidget2 = makeWidget({ - id: 'fields-2', - type: WidgetType.FIELDS, - }); - const dynamicWidget = makeDynamicRelationWidget('rel-1'); - - const persistedLayout = makePersistedLayout([ - makeTab('tab-1', [fieldsWidget1]), - makeTab('tab-2', [fieldsWidget2]), - ]); - const draft = makeDraftLayout([ - makeTab('tab-1', [fieldsWidget1, dynamicWidget]), - makeTab('tab-2', [fieldsWidget2]), - ]); - - const result = reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - draft, - ); - - // Tab 1 should have dynamic widget injected - expect(result.tabs[0].widgets).toHaveLength(2); - // Tab 2 should remain unchanged - expect(result.tabs[1].widgets).toHaveLength(1); - }); - - it('should inject multiple dynamic widgets', () => { - const fieldsWidget = makeWidget({ - id: 'fields-1', - type: WidgetType.FIELDS, - }); - const dynamicWidget1 = makeDynamicRelationWidget('rel-1'); - const dynamicWidget2 = makeDynamicRelationWidget('rel-2'); - - const persistedLayout = makePersistedLayout([ - makeTab('tab-1', [fieldsWidget]), - ]); - const draft = makeDraftLayout([ - makeTab('tab-1', [fieldsWidget, dynamicWidget1, dynamicWidget2]), - ]); - - const result = reInjectDynamicRelationWidgetsFromDraft( - persistedLayout, - draft, - ); - - expect(result.tabs[0].widgets).toHaveLength(3); - expect(result.tabs[0].widgets[0].id).toBe('fields-1'); - expect(result.tabs[0].widgets[1].id).toBe( - `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}rel-1`, - ); - expect(result.tabs[0].widgets[2].id).toBe( - `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}rel-2`, - ); - }); -}); diff --git a/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts b/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts index 7ba93cebb1..502f5fe30e 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/__tests__/shouldEnableTabEditingFeatures.test.ts @@ -14,61 +14,13 @@ describe('shouldEnableTabEditingFeatures', () => { expect(result).toBe(true); }); - it('should return false for RECORD_PAGE layout type without flag', () => { + it('should return true for RECORD_PAGE layout type', () => { const result = shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE); - expect(result).toBe(false); - }); - - it('should return true for RECORD_PAGE layout type with flag enabled', () => { - const result = shouldEnableTabEditingFeatures( - PageLayoutType.RECORD_PAGE, - true, - ); expect(result).toBe(true); }); - it('should return false for RECORD_PAGE layout type with flag disabled', () => { - const result = shouldEnableTabEditingFeatures( - PageLayoutType.RECORD_PAGE, - false, - ); - expect(result).toBe(false); - }); - it('should return false for RECORD_INDEX layout type', () => { const result = shouldEnableTabEditingFeatures(PageLayoutType.RECORD_INDEX); expect(result).toBe(false); }); - - it('should return false for RECORD_INDEX layout type even with flag', () => { - const result = shouldEnableTabEditingFeatures( - PageLayoutType.RECORD_INDEX, - true, - ); - expect(result).toBe(false); - }); - - describe('behavior validation', () => { - it('should enable tab editing features only for dashboards, standalone pages, and record pages with flag', () => { - expect(shouldEnableTabEditingFeatures(PageLayoutType.DASHBOARD)).toBe( - true, - ); - - expect( - shouldEnableTabEditingFeatures(PageLayoutType.STANDALONE_PAGE), - ).toBe(true); - - expect(shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE)).toBe( - false, - ); - - expect( - shouldEnableTabEditingFeatures(PageLayoutType.RECORD_PAGE, true), - ).toBe(true); - - expect(shouldEnableTabEditingFeatures(PageLayoutType.RECORD_INDEX)).toBe( - false, - ); - }); - }); }); diff --git a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts index eeb990c31e..a862b81dd7 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/convertPageLayoutDraftToUpdateInput.ts @@ -1,6 +1,5 @@ import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout'; import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { isDynamicRelationWidget } from '@/page-layout/utils/isDynamicRelationWidget'; import { PageLayoutTabLayoutMode, type UpdatePageLayoutWithTabsInput, @@ -40,10 +39,7 @@ const buildWidgetPosition = ( export const convertPageLayoutDraftToUpdateInput = ( pageLayoutDraft: DraftPageLayout, - options?: { shouldFilterDynamicRelationWidgets?: boolean }, ): UpdatePageLayoutWithTabsInput => { - const shouldFilter = options?.shouldFilterDynamicRelationWidgets ?? false; - return { name: pageLayoutDraft.name, type: pageLayoutDraft.type, @@ -51,17 +47,13 @@ export const convertPageLayoutDraftToUpdateInput = ( tabs: pageLayoutDraft.tabs .filter((tab) => tab.isActive) .map((tab) => { - const widgets = shouldFilter - ? tab.widgets.filter((widget) => !isDynamicRelationWidget(widget)) - : tab.widgets; - return { id: tab.id, title: tab.title, position: tab.position, icon: tab.icon ?? null, layoutMode: tab.layoutMode, - widgets: widgets.map((widget, widgetIndex) => ({ + widgets: tab.widgets.map((widget, widgetIndex) => ({ id: widget.id, pageLayoutTabId: widget.pageLayoutTabId, title: widget.title, diff --git a/packages/twenty-front/src/modules/page-layout/utils/injectRelationWidgetsIntoLayout.ts b/packages/twenty-front/src/modules/page-layout/utils/injectRelationWidgetsIntoLayout.ts deleted file mode 100644 index 71d229326f..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/injectRelationWidgetsIntoLayout.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { DYNAMIC_RELATION_WIDGET_ID_PREFIX } from '@/page-layout/utils/isDynamicRelationWidget'; -import { isDefined } from 'twenty-shared/utils'; -import { - FieldDisplayMode, - PageLayoutTabLayoutMode, - WidgetConfigurationType, - WidgetType, -} from '~/generated-metadata/graphql'; - -const getRelationFieldWidgetToInsert = ( - field: FieldMetadataItem, - tabId: string, -): PageLayoutWidget => ({ - __typename: 'PageLayoutWidget' as const, - id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}${field.id}-${field.label}`, - applicationId: '', - pageLayoutTabId: tabId, - title: field.label, - isActive: true, - type: WidgetType.FIELD, - objectMetadataId: null, - gridPosition: { - __typename: 'GridPosition' as const, - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - position: { - __typename: 'PageLayoutWidgetGridPosition' as const, - layoutMode: PageLayoutTabLayoutMode.GRID, - row: 0, - column: 0, - rowSpan: 1, - columnSpan: 12, - }, - configuration: { - __typename: 'FieldConfiguration' as const, - configurationType: WidgetConfigurationType.FIELD, - fieldMetadataId: field.id, - fieldDisplayMode: FieldDisplayMode.CARD, - }, - createdAt: '2024-01-01T00:00:00.000Z', - updatedAt: '2024-01-01T00:00:00.000Z', - deletedAt: null, -}); - -const getRelationFieldWidgetsToInsert = ( - relationFields: FieldMetadataItem[], - tabId: string, -): PageLayoutWidget[] => { - return relationFields.map((field) => - getRelationFieldWidgetToInsert(field, tabId), - ); -}; - -export const injectRelationWidgetsIntoLayout = ( - layout: PageLayout, - boxedRelationFieldMetadataItems: FieldMetadataItem[], -): PageLayout => { - if (boxedRelationFieldMetadataItems.length === 0) { - return layout; - } - - const firstTab = layout.tabs[0]; - if (!isDefined(firstTab)) { - return layout; - } - - const relationWidgets = getRelationFieldWidgetsToInsert( - boxedRelationFieldMetadataItems, - firstTab.id, - ); - - return { - ...layout, - tabs: layout.tabs.map((tab) => { - if (tab.id === firstTab.id) { - const firstFieldsWidgetIndex = tab.widgets.findIndex( - (widget) => widget.type === WidgetType.FIELDS, - ); - - if (firstFieldsWidgetIndex === -1) { - return { - ...tab, - widgets: [...tab.widgets, ...relationWidgets], - }; - } - - // TODO: This note widget repositioning logic is temporary and will be deleted soon. - // We need this to ensure the note editor is displayed before record relations, - // matching the behavior of the old show page. - const noteWidgetIndex = tab.widgets.findIndex( - (widget) => widget.type === WidgetType.NOTES, - ); - - const widgetsBeforeRelation = tab.widgets.slice( - 0, - firstFieldsWidgetIndex + 1, - ); - const widgetsAfterRelation = - noteWidgetIndex === -1 - ? tab.widgets.slice(firstFieldsWidgetIndex + 1) - : [ - ...tab.widgets.slice( - firstFieldsWidgetIndex + 1, - noteWidgetIndex, - ), - ...tab.widgets.slice(noteWidgetIndex + 1), - ]; - - const noteWidget = - noteWidgetIndex !== -1 ? [tab.widgets[noteWidgetIndex]] : []; - - return { - ...tab, - widgets: [ - ...widgetsBeforeRelation, - ...relationWidgets, - ...noteWidget, - ...widgetsAfterRelation, - ], - }; - } - return tab; - }), - }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/isDynamicRelationWidget.ts b/packages/twenty-front/src/modules/page-layout/utils/isDynamicRelationWidget.ts deleted file mode 100644 index 4ed4a6fc10..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/isDynamicRelationWidget.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; - -export const DYNAMIC_RELATION_WIDGET_ID_PREFIX = 'dynamic-relation-widget-'; - -export const isDynamicRelationWidget = (widget: PageLayoutWidget): boolean => { - return widget.id.startsWith(DYNAMIC_RELATION_WIDGET_ID_PREFIX); -}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft.ts b/packages/twenty-front/src/modules/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft.ts deleted file mode 100644 index 5ada10f484..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/reInjectDynamicRelationWidgetsFromDraft.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { isDynamicRelationWidget } from '@/page-layout/utils/isDynamicRelationWidget'; -import { WidgetType } from '~/generated-metadata/graphql'; - -export const reInjectDynamicRelationWidgetsFromDraft = ( - persistedLayout: PageLayout, - previousDraft: DraftPageLayout, -): PageLayout => { - const dynamicWidgetsByTabId = new Map( - previousDraft.tabs.map((tab) => [ - tab.id, - tab.widgets.filter(isDynamicRelationWidget), - ]), - ); - - const hasDynamicWidgets = [...dynamicWidgetsByTabId.values()].some( - (widgets) => widgets.length > 0, - ); - - if (!hasDynamicWidgets) { - return persistedLayout; - } - - return { - ...persistedLayout, - tabs: persistedLayout.tabs.map((tab) => { - const relationWidgets = dynamicWidgetsByTabId.get(tab.id); - - if (!relationWidgets || relationWidgets.length === 0) { - return tab; - } - - const firstFieldsWidgetIndex = tab.widgets.findIndex( - (widget) => widget.type === WidgetType.FIELDS, - ); - - if (firstFieldsWidgetIndex === -1) { - return { - ...tab, - widgets: [...tab.widgets, ...relationWidgets], - }; - } - - const widgetsBefore = tab.widgets.slice(0, firstFieldsWidgetIndex + 1); - const widgetsAfter = tab.widgets.slice(firstFieldsWidgetIndex + 1); - - return { - ...tab, - widgets: [...widgetsBefore, ...relationWidgets, ...widgetsAfter], - }; - }), - }; -}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts b/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts index 724d32df2d..e006818d2b 100644 --- a/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts +++ b/packages/twenty-front/src/modules/page-layout/utils/shouldEnableTabEditingFeatures.ts @@ -2,21 +2,10 @@ import { PageLayoutType } from '~/generated-metadata/graphql'; export const shouldEnableTabEditingFeatures = ( pageLayoutType: PageLayoutType, - isRecordPageGlobalEditionEnabled?: boolean, ): boolean => { - if ( + return ( pageLayoutType === PageLayoutType.DASHBOARD || - pageLayoutType === PageLayoutType.STANDALONE_PAGE - ) { - return true; - } - - if ( - pageLayoutType === PageLayoutType.RECORD_PAGE && - isRecordPageGlobalEditionEnabled - ) { - return true; - } - - return false; + pageLayoutType === PageLayoutType.STANDALONE_PAGE || + pageLayoutType === PageLayoutType.RECORD_PAGE + ); }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx index 2576c0d56e..3bb4166a34 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/RecordPageWidgetRenderer.tsx @@ -2,18 +2,6 @@ import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { WidgetCardShell } from '@/page-layout/widgets/components/WidgetCardShell'; import { useWidgetActions } from '@/page-layout/widgets/hooks/useWidgetActions'; import { useWidgetRendererState } from '@/page-layout/widgets/hooks/useWidgetRendererState'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { styled } from '@linaria/react'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { - FeatureFlagKey, - PageLayoutType, - WidgetType, -} from '~/generated-metadata/graphql'; - -const StyledEditingWidgetWrapper = styled.div` - padding: ${themeCssVariables.spacing[2]}; -`; type RecordPageWidgetRendererProps = { widget: PageLayoutWidget; @@ -24,39 +12,13 @@ export const RecordPageWidgetRenderer = ({ }: RecordPageWidgetRendererProps) => { const state = useWidgetRendererState(widget); - const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - ); - - const isRecordPageLayout = - state.currentPageLayout.type === PageLayoutType.RECORD_PAGE; - - const isReorderEnabled = - !isRecordPageLayout || - (isRecordPageLayout && isRecordPageGlobalEditionEnabled); - - const isDeletingWidgetEnabled = - !isRecordPageLayout || - (isRecordPageLayout && isRecordPageGlobalEditionEnabled); - - const isWidgetEditable = - state.isPageLayoutInEditMode && - (!isRecordPageLayout || - (isRecordPageLayout && isRecordPageGlobalEditionEnabled) || - widget.type === WidgetType.FIELDS || - widget.type === WidgetType.FIELD); + const isWidgetEditable = state.isPageLayoutInEditMode; const actions = useWidgetActions({ widget }); - // TODO: remove once all record page layouts widgets use the editable contain in edit mode - const shouldWrapWithEditingWrapper = - isWidgetEditable && - state.variant === 'side-column' && - !isRecordPageGlobalEditionEnabled; - const isCanvasVariant = state.variant === 'canvas'; - const shell = ( + return ( ); - - if (shouldWrapWithEditingWrapper) { - return {shell}; - } - - return shell; }; 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 8bb52491ce..d6057dd77e 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx @@ -81,9 +81,6 @@ export const SettingsObjectDetailPage = () => { const isUniqueIndexesEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_UNIQUE_INDEXES_ENABLED, ); - const isRecordPageLayoutEditingEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED, - ); const [isDeleting, setIsDeleting] = useState(false); @@ -123,7 +120,6 @@ export const SettingsObjectDetailPage = () => { title: t`Layout`, Icon: IconLayout, hide: - !isRecordPageLayoutEditingEnabled || objectMetadataItem.isRemote || objectMetadataItem.nameSingular === CoreObjectNameSingular.Dashboard, }, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts index 3be428364a..2e5f185049 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts @@ -1,5 +1,5 @@ import { Command } from 'nest-commander'; -import { FeatureFlagKey, ViewType } from 'twenty-shared/types'; +import { type FeatureFlagKey, ViewType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner'; @@ -47,7 +47,7 @@ export class BackfillRecordPageLayoutsCommand extends ActiveOrSuspendedWorkspace const isDryRun = options.dryRun ?? false; const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED, + 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as FeatureFlagKey, workspaceId, ); @@ -88,12 +88,12 @@ export class BackfillRecordPageLayoutsCommand extends ActiveOrSuspendedWorkspace }); await this.featureFlagService.enableFeatureFlags( - [FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED], + ['IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as FeatureFlagKey], workspaceId, ); await this.featureFlagService.enableFeatureFlags( - [FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED], + ['IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as FeatureFlagKey], workspaceId, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts index d3caadf63a..fd923c6c8d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type FindOneOptions, type Repository } from 'typeorm'; @@ -378,7 +377,6 @@ export class FieldMetadataService extends TypeOrmQueryService ({ objectMetadataUniversalIdentifier: flatFieldMetadata.objectMetadataUniversalIdentifier, @@ -445,7 +435,6 @@ export class FieldMetadataService extends TypeOrmQueryService { IS_UNIQUE_INDEXES_ENABLED: false, IS_JSON_FILTER_ENABLED: false, IS_MARKETPLACE_SETTING_TAB_VISIBLE: false, - IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false, IS_PUBLIC_DOMAIN_ENABLED: false, IS_EMAILING_DOMAIN_ENABLED: false, IS_EMAIL_GROUP_ENABLED: false, IS_JUNCTION_RELATIONS_ENABLED: false, - IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: false, IS_REST_METADATA_API_NEW_FORMAT_DIRECT: false, }, userWorkspaceRoleMap: {}, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index de6091ac8f..61aab31f57 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -50,16 +50,6 @@ export const seedFeatureFlags = async ({ workspaceId: workspaceId, value: true, }, - { - key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED, - workspaceId: workspaceId, - value: true, - }, - { - key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - workspaceId: workspaceId, - value: true, - }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts index c66087e504..38cebc58df 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts @@ -264,7 +264,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = { shortLabel: 'Edit Layout', availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION, conditionalAvailabilityExpression: - 'pageType == "RECORD_PAGE" and featureFlags.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED and not isLayoutCustomizationModeEnabled and noneDefined(selectedRecords, "deletedAt") and objectPermissions.canUpdateObjectRecords and objectMetadataItem.nameSingular != "dashboard"', + 'pageType == "RECORD_PAGE" and not isLayoutCustomizationModeEnabled and noneDefined(selectedRecords, "deletedAt") and objectPermissions.canUpdateObjectRecords and objectMetadataItem.nameSingular != "dashboard"', availabilityObjectMetadataUniversalIdentifier: null, frontComponentUniversalIdentifier: null, engineComponentKey: EngineComponentKey.EDIT_RECORD_PAGE_LAYOUT, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts index 33b1242039..a6e31f106e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts @@ -1,7 +1,5 @@ import { FeatureFlagKey } from 'twenty-shared/types'; export const DEFAULT_FEATURE_FLAGS = [ - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED, - FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED, FeatureFlagKey.IS_REST_METADATA_API_NEW_FORMAT_DIRECT, ] as const satisfies FeatureFlagKey[]; diff --git a/packages/twenty-shared/src/types/FeatureFlagKey.ts b/packages/twenty-shared/src/types/FeatureFlagKey.ts index be20bb1247..064b016666 100644 --- a/packages/twenty-shared/src/types/FeatureFlagKey.ts +++ b/packages/twenty-shared/src/types/FeatureFlagKey.ts @@ -2,11 +2,9 @@ export enum FeatureFlagKey { IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE', - IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', IS_EMAIL_GROUP_ENABLED = 'IS_EMAIL_GROUP_ENABLED', IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED', - IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT', }