Migrate field widgets to backend (#18808)
- Renamed FieldConfiguration's layout field to fieldDisplayMode as it caused issues with the layout field of BarChartConfiguration - Create relation Field widgets for standard objects --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
d90d2e3151
commit
be89ef30cd
File diff suppressed because one or more lines are too long
+12
-3
@@ -1,9 +1,9 @@
|
||||
import { useExitLayoutCustomizationMode } from '@/layout-customization/hooks/useExitLayoutCustomizationMode';
|
||||
import { activeCustomizationPageLayoutIdsState } from '@/layout-customization/states/activeCustomizationPageLayoutIdsState';
|
||||
import { useSaveNavigationMenuItemsDraft } from '@/navigation-menu-item/edit/hooks/useSaveNavigationMenuItemsDraft';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/common/states/navigationMenuItemsSelector';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/common/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { useSaveNavigationMenuItemsDraft } from '@/navigation-menu-item/edit/hooks/useSaveNavigationMenuItemsDraft';
|
||||
import { useSaveFieldsWidgetGroups } from '@/page-layout/hooks/useSaveFieldsWidgetGroups';
|
||||
import { useUpdatePageLayoutWithTabsAndWidgets } from '@/page-layout/hooks/useUpdatePageLayoutWithTabsAndWidgets';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
@@ -16,11 +16,12 @@ import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLa
|
||||
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 { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
@@ -36,6 +37,10 @@ export const useSaveLayoutCustomization = () => {
|
||||
const { exitLayoutCustomizationMode } = useExitLayoutCustomizationMode();
|
||||
const { saveFieldsWidgetGroups } = useSaveFieldsWidgetGroups();
|
||||
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isRecordPageLayoutEditingEnabled =
|
||||
featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED];
|
||||
|
||||
const save = useCallback(async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
@@ -92,7 +97,10 @@ export const useSaveLayoutCustomization = () => {
|
||||
);
|
||||
|
||||
if (isPageLayoutStructureDirty) {
|
||||
const updateInput = convertPageLayoutDraftToUpdateInput(draft);
|
||||
const updateInput = convertPageLayoutDraftToUpdateInput(draft, {
|
||||
shouldFilterDynamicRelationWidgets:
|
||||
!isRecordPageLayoutEditingEnabled,
|
||||
});
|
||||
const result = await updatePageLayoutWithTabsAndWidgets(
|
||||
pageLayoutId,
|
||||
updateInput,
|
||||
@@ -107,6 +115,7 @@ export const useSaveLayoutCustomization = () => {
|
||||
transformPageLayout(updatedPageLayout);
|
||||
|
||||
const pageLayoutToPersist =
|
||||
!isRecordPageLayoutEditingEnabled &&
|
||||
persistedLayout.type === PageLayoutType.RECORD_PAGE
|
||||
? reInjectDynamicRelationWidgetsFromDraft(
|
||||
persistedLayout,
|
||||
|
||||
+12
-3
@@ -10,10 +10,11 @@ import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLa
|
||||
import { isPageLayoutEmpty } from '@/page-layout/utils/isPageLayoutEmpty';
|
||||
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 { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type PageLayoutInitializationQueryEffectProps = {
|
||||
@@ -26,9 +27,17 @@ export const PageLayoutInitializationQueryEffect = ({
|
||||
const [pageLayoutIsInitialized, setPageLayoutIsInitialized] =
|
||||
useAtomComponentState(pageLayoutIsInitializedComponentState);
|
||||
|
||||
const basePageLayout = useBasePageLayout(pageLayoutId);
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isRecordPageLayoutEditingEnabled =
|
||||
featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED];
|
||||
|
||||
const pageLayout = usePageLayoutWithRelationWidgets(basePageLayout);
|
||||
const basePageLayout = useBasePageLayout(pageLayoutId);
|
||||
const pageLayoutWithRelationWidgets =
|
||||
usePageLayoutWithRelationWidgets(basePageLayout);
|
||||
|
||||
const pageLayout = isRecordPageLayoutEditingEnabled
|
||||
? basePageLayout
|
||||
: pageLayoutWithRelationWidgets;
|
||||
|
||||
const { setIsPageLayoutInEditMode } =
|
||||
useSetIsPageLayoutInEditMode(pageLayoutId);
|
||||
|
||||
@@ -7,8 +7,10 @@ import { PageLayoutComponentInstanceContext } from '@/page-layout/states/context
|
||||
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 } from '~/generated-metadata/graphql';
|
||||
|
||||
type PageLayoutRendererProps = {
|
||||
pageLayoutId: string;
|
||||
@@ -19,6 +21,10 @@ 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,
|
||||
@@ -42,7 +48,9 @@ export const PageLayoutRenderer = ({
|
||||
>
|
||||
<PageLayoutInitializationQueryEffect pageLayoutId={pageLayoutId} />
|
||||
<PageLayoutRecordPageCustomizationSessionRegistrationEffect />
|
||||
<PageLayoutRelationWidgetsSyncEffect pageLayoutId={pageLayoutId} />
|
||||
{!isRecordPageLayoutEditingEnabled && (
|
||||
<PageLayoutRelationWidgetsSyncEffect pageLayoutId={pageLayoutId} />
|
||||
)}
|
||||
<PageLayoutRendererContent />
|
||||
</PageLayoutEditModeProvider>
|
||||
</TabListComponentInstanceContext.Provider>
|
||||
|
||||
+2
@@ -161,6 +161,8 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
}
|
||||
... on FieldConfiguration {
|
||||
configurationType
|
||||
fieldDisplayMode
|
||||
fieldMetadataId
|
||||
}
|
||||
... on FieldRichTextConfiguration {
|
||||
configurationType
|
||||
|
||||
@@ -10,10 +10,11 @@ import { reInjectDynamicRelationWidgetsFromDraft } from '@/page-layout/utils/reI
|
||||
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 { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
|
||||
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
|
||||
@@ -40,11 +41,17 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
|
||||
const { updatePageLayoutWithTabsAndWidgets } =
|
||||
useUpdatePageLayoutWithTabsAndWidgets();
|
||||
|
||||
const featureFlags = useFeatureFlagsMap();
|
||||
const isRecordPageLayoutEditingEnabled =
|
||||
featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED];
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const savePageLayout = useCallback(async () => {
|
||||
const pageLayoutDraft = store.get(pageLayoutDraftCallbackState);
|
||||
const updateInput = convertPageLayoutDraftToUpdateInput(pageLayoutDraft);
|
||||
const updateInput = convertPageLayoutDraftToUpdateInput(pageLayoutDraft, {
|
||||
shouldFilterDynamicRelationWidgets: !isRecordPageLayoutEditingEnabled,
|
||||
});
|
||||
|
||||
const result = await updatePageLayoutWithTabsAndWidgets(
|
||||
pageLayoutId,
|
||||
@@ -60,6 +67,7 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
|
||||
transformPageLayout(updatedPageLayout);
|
||||
|
||||
const pageLayoutToPersist =
|
||||
!isRecordPageLayoutEditingEnabled &&
|
||||
persistedLayout.type === PageLayoutType.RECORD_PAGE
|
||||
? reInjectDynamicRelationWidgetsFromDraft(
|
||||
persistedLayout,
|
||||
@@ -77,6 +85,7 @@ export const useSavePageLayout = (pageLayoutIdFromProps: string) => {
|
||||
|
||||
return result;
|
||||
}, [
|
||||
isRecordPageLayoutEditingEnabled,
|
||||
pageLayoutCurrentLayoutsCallbackState,
|
||||
pageLayoutDraftCallbackState,
|
||||
pageLayoutId,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { type WidgetConfigurationType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type FieldDisplayMode,
|
||||
type WidgetConfigurationType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export type FieldConfiguration = {
|
||||
__typename: 'FieldConfiguration';
|
||||
configurationType: WidgetConfigurationType.FIELD;
|
||||
fieldMetadataId: string;
|
||||
layout: 'FIELD' | 'CARD' | 'VIEW';
|
||||
fieldDisplayMode: FieldDisplayMode;
|
||||
};
|
||||
|
||||
+92
-14
@@ -60,19 +60,19 @@ describe('convertPageLayoutDraftToUpdateInput', () => {
|
||||
expect(result.tabs[0].widgets[0].id).toBe('w1');
|
||||
});
|
||||
|
||||
it('should filter out dynamic relation widgets', () => {
|
||||
const regularWidget = makeWidget({ id: 'w1' });
|
||||
const dynamicWidget = makeWidget({
|
||||
id: `${DYNAMIC_RELATION_WIDGET_ID_PREFIX}relation-1`,
|
||||
type: WidgetType.VIEW,
|
||||
it('should handle multiple widget types', () => {
|
||||
const fieldsWidget = makeWidget({ id: 'w1', type: WidgetType.FIELDS });
|
||||
const timelineWidget = makeWidget({
|
||||
id: 'w2',
|
||||
type: WidgetType.TIMELINE,
|
||||
});
|
||||
|
||||
const draft = makeDraft([makeTab('tab-1', [regularWidget, dynamicWidget])]);
|
||||
const draft = makeDraft([makeTab('tab-1', [fieldsWidget, timelineWidget])]);
|
||||
|
||||
const result = convertPageLayoutDraftToUpdateInput(draft);
|
||||
|
||||
expect(result.tabs[0].widgets).toHaveLength(1);
|
||||
expect(result.tabs[0].widgets[0].id).toBe('w1');
|
||||
expect(result.tabs[0].widgets).toHaveLength(2);
|
||||
expect(result.tabs[0].widgets.map((w) => w.id)).toEqual(['w1', 'w2']);
|
||||
});
|
||||
|
||||
it('should map gridPosition correctly', () => {
|
||||
@@ -121,24 +121,25 @@ describe('convertPageLayoutDraftToUpdateInput', () => {
|
||||
expect(result.tabs[1].id).toBe('tab-2');
|
||||
});
|
||||
|
||||
it('should keep all non-dynamic widgets when multiple widget types exist', () => {
|
||||
it('should keep all widgets when 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 fieldWidget = makeWidget({
|
||||
id: 'w3',
|
||||
type: WidgetType.FIELD,
|
||||
});
|
||||
|
||||
const draft = makeDraft([
|
||||
makeTab('tab-1', [fieldsWidget, dynamicWidget, timelineWidget]),
|
||||
makeTab('tab-1', [fieldsWidget, fieldWidget, timelineWidget]),
|
||||
]);
|
||||
|
||||
const result = convertPageLayoutDraftToUpdateInput(draft);
|
||||
|
||||
expect(result.tabs[0].widgets).toHaveLength(2);
|
||||
expect(result.tabs[0].widgets.map((w) => w.id)).toEqual(['w1', 'w2']);
|
||||
expect(result.tabs[0].widgets).toHaveLength(3);
|
||||
expect(result.tabs[0].widgets.map((w) => w.id)).toEqual(['w1', 'w3', 'w2']);
|
||||
});
|
||||
|
||||
it('should produce VERTICAL_LIST position with index from widget.position when tab is VERTICAL_LIST', () => {
|
||||
@@ -326,4 +327,81 @@ 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -195,7 +196,7 @@ describe('injectRelationWidgetsIntoLayout', () => {
|
||||
expect(injectedWidget.configuration).toEqual(
|
||||
expect.objectContaining({
|
||||
fieldMetadataId: 'f1',
|
||||
layout: 'CARD',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
+16
-9
@@ -40,19 +40,25 @@ const buildWidgetPosition = (
|
||||
|
||||
export const convertPageLayoutDraftToUpdateInput = (
|
||||
pageLayoutDraft: DraftPageLayout,
|
||||
options?: { shouldFilterDynamicRelationWidgets?: boolean },
|
||||
): UpdatePageLayoutWithTabsInput => {
|
||||
const shouldFilter = options?.shouldFilterDynamicRelationWidgets ?? false;
|
||||
|
||||
return {
|
||||
name: pageLayoutDraft.name,
|
||||
type: pageLayoutDraft.type,
|
||||
objectMetadataId: pageLayoutDraft.objectMetadataId ?? null,
|
||||
tabs: pageLayoutDraft.tabs.map((tab) => ({
|
||||
id: tab.id,
|
||||
title: tab.title,
|
||||
position: tab.position,
|
||||
layoutMode: tab.layoutMode,
|
||||
widgets: tab.widgets
|
||||
.filter((widget) => !isDynamicRelationWidget(widget))
|
||||
.map((widget, widgetIndex) => ({
|
||||
tabs: pageLayoutDraft.tabs.map((tab) => {
|
||||
const widgets = shouldFilter
|
||||
? tab.widgets.filter((widget) => !isDynamicRelationWidget(widget))
|
||||
: tab.widgets;
|
||||
|
||||
return {
|
||||
id: tab.id,
|
||||
title: tab.title,
|
||||
position: tab.position,
|
||||
layoutMode: tab.layoutMode,
|
||||
widgets: widgets.map((widget, widgetIndex) => ({
|
||||
id: widget.id,
|
||||
pageLayoutTabId: widget.pageLayoutTabId,
|
||||
title: widget.title,
|
||||
@@ -71,6 +77,7 @@ export const convertPageLayoutDraftToUpdateInput = (
|
||||
),
|
||||
configuration: widget.configuration ?? null,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import {
|
||||
FieldDisplayMode,
|
||||
PageLayoutTabLayoutMode,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
@@ -28,7 +29,7 @@ export const createDefaultFieldWidget = ({
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId,
|
||||
layout: 'CARD',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
gridPosition: {
|
||||
__typename: 'GridPosition',
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ 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,
|
||||
@@ -38,7 +39,7 @@ const getRelationFieldWidgetToInsert = (
|
||||
__typename: 'FieldConfiguration' as const,
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
layout: 'CARD' as const,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
|
||||
+10
-9
@@ -7,14 +7,14 @@ import { expect, within } from 'storybook/test';
|
||||
import { CatalogDecorator, type CatalogStory } from 'twenty-ui/testing';
|
||||
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
|
||||
import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
@@ -35,14 +35,15 @@ import { widgetCardHoveredComponentFamilyState } from '@/page-layout/widgets/sta
|
||||
import { type WidgetCardVariant } from '@/page-layout/widgets/types/WidgetCardVariant';
|
||||
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import {
|
||||
GraphOrderBy,
|
||||
WidgetType,
|
||||
AggregateOperations,
|
||||
AxisNameDisplay,
|
||||
BarChartLayout,
|
||||
FieldDisplayMode,
|
||||
GraphOrderBy,
|
||||
PageLayoutTabLayoutMode,
|
||||
PageLayoutType,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
@@ -1023,7 +1024,7 @@ export const WithManyToOneRelationFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: accountOwnerField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1142,7 +1143,7 @@ export const WithOneToManyRelationFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: companyPeopleField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1253,7 +1254,7 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: companyPeopleField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
|
||||
+5
-4
@@ -15,9 +15,9 @@ import { assertFieldWidgetOrThrow } from '@/page-layout/widgets/field/utils/asse
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
|
||||
import { SidePanelProvider } from '@/ui/layout/side-panel/contexts/SidePanelContext';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
} from 'twenty-ui/layout';
|
||||
import { FieldDisplayMode } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
@@ -92,10 +93,10 @@ export const FieldWidget = ({ widget }: FieldWidgetProps) => {
|
||||
labelWidth: 90,
|
||||
});
|
||||
|
||||
const layout = widget.configuration.layout;
|
||||
const fieldDisplayMode = widget.configuration.fieldDisplayMode;
|
||||
|
||||
if (isFieldMorphRelation(fieldDefinition)) {
|
||||
if (layout === 'CARD') {
|
||||
if (fieldDisplayMode === FieldDisplayMode.CARD) {
|
||||
return (
|
||||
<FieldWidgetMorphRelationCard
|
||||
fieldDefinition={fieldDefinition}
|
||||
@@ -115,7 +116,7 @@ export const FieldWidget = ({ widget }: FieldWidgetProps) => {
|
||||
}
|
||||
|
||||
if (isFieldRelation(fieldDefinition)) {
|
||||
if (layout === 'CARD') {
|
||||
if (fieldDisplayMode === FieldDisplayMode.CARD) {
|
||||
return (
|
||||
<FieldWidgetRelationCard
|
||||
fieldDefinition={fieldDefinition}
|
||||
|
||||
+21
-20
@@ -4,10 +4,8 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { isMinimalMetadataReadyState } from '@/metadata-store/states/isMinimalMetadataReadyState';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
@@ -23,8 +21,10 @@ import { FieldWidget } from '@/page-layout/widgets/field/components/FieldWidget'
|
||||
import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext';
|
||||
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import {
|
||||
FieldDisplayMode,
|
||||
PageLayoutTabLayoutMode,
|
||||
PageLayoutType,
|
||||
WidgetConfigurationType,
|
||||
@@ -36,6 +36,7 @@ import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMeta
|
||||
import { getTestEnrichedObjectMetadataItemsMock } from '~/testing/utils/getTestEnrichedObjectMetadataItemsMock';
|
||||
import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
import { setTestObjectMetadataItemsInMetadataStore } from '~/testing/utils/setTestObjectMetadataItemsInMetadataStore';
|
||||
|
||||
const companyObjectMetadataItem = getMockObjectMetadataItemOrThrow(
|
||||
CoreObjectNameSingular.Company,
|
||||
@@ -348,7 +349,7 @@ export const TextFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: nameField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -442,7 +443,7 @@ export const AddressFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: addressField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -539,7 +540,7 @@ export const NumberFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: employeesField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -633,7 +634,7 @@ export const LinkFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: linkedinField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -727,7 +728,7 @@ export const ManyToOneRelationFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: accountOwnerField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -831,7 +832,7 @@ export const OneToManyRelationFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: companyPeopleField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -927,7 +928,7 @@ export const BooleanFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: idealCustomerProfileField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1020,7 +1021,7 @@ export const CurrencyFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: annualRecurringRevenueField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1113,7 +1114,7 @@ export const EmailsFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: personEmailsField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1207,7 +1208,7 @@ export const PhonesFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: personPhonesField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1301,7 +1302,7 @@ export const SelectFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: opportunityStageField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1396,7 +1397,7 @@ export const MultiSelectFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: companyWorkPolicyField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1494,7 +1495,7 @@ export const TimelineActivityRelationFieldWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: timelineActivityWorkspaceMemberField.id,
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1593,7 +1594,7 @@ export const ManyToOneRelationCardWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: accountOwnerField.id,
|
||||
layout: 'CARD',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1705,7 +1706,7 @@ export const OneToManyRelationCardWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: companyPeopleField.id,
|
||||
layout: 'CARD',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1800,7 +1801,7 @@ export const TimelineActivityRelationCardWidget: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: timelineActivityWorkspaceMemberField.id,
|
||||
layout: 'CARD',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
@@ -1961,7 +1962,7 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: companyPeopleField.id,
|
||||
layout: 'CARD',
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
isOverridden: false,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { assertFieldWidgetOrThrow } from '@/page-layout/widgets/field/utils/assertFieldWidgetOrThrow';
|
||||
import {
|
||||
FieldDisplayMode,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@@ -14,7 +15,7 @@ describe('assertFieldWidgetOrThrow', () => {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: 'fm-1',
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
} as PageLayoutWidget;
|
||||
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
|
||||
import { isFieldWidget } from '@/page-layout/widgets/field/utils/isFieldWidget';
|
||||
import {
|
||||
FieldDisplayMode,
|
||||
WidgetConfigurationType,
|
||||
WidgetType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@@ -14,7 +15,7 @@ describe('isFieldWidget', () => {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: 'fm-1',
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
} as PageLayoutWidget;
|
||||
|
||||
@@ -29,7 +30,7 @@ describe('isFieldWidget', () => {
|
||||
__typename: 'FieldConfiguration',
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: 'fm-1',
|
||||
layout: 'FIELD',
|
||||
fieldDisplayMode: FieldDisplayMode.FIELD,
|
||||
},
|
||||
} as PageLayoutWidget;
|
||||
|
||||
|
||||
@@ -1156,6 +1156,15 @@ type EmailsConfiguration {
|
||||
|
||||
type FieldConfiguration {
|
||||
configurationType: WidgetConfigurationType!
|
||||
fieldMetadataId: String!
|
||||
fieldDisplayMode: FieldDisplayMode!
|
||||
}
|
||||
|
||||
"""Display mode for field configuration widgets"""
|
||||
enum FieldDisplayMode {
|
||||
CARD
|
||||
FIELD
|
||||
VIEW
|
||||
}
|
||||
|
||||
type FieldRichTextConfiguration {
|
||||
|
||||
@@ -874,9 +874,15 @@ export interface EmailsConfiguration {
|
||||
|
||||
export interface FieldConfiguration {
|
||||
configurationType: WidgetConfigurationType
|
||||
fieldMetadataId: Scalars['String']
|
||||
fieldDisplayMode: FieldDisplayMode
|
||||
__typename: 'FieldConfiguration'
|
||||
}
|
||||
|
||||
|
||||
/** Display mode for field configuration widgets */
|
||||
export type FieldDisplayMode = 'CARD' | 'FIELD' | 'VIEW'
|
||||
|
||||
export interface FieldRichTextConfiguration {
|
||||
configurationType: WidgetConfigurationType
|
||||
__typename: 'FieldRichTextConfiguration'
|
||||
@@ -3933,6 +3939,8 @@ export interface EmailsConfigurationGenqlSelection{
|
||||
|
||||
export interface FieldConfigurationGenqlSelection{
|
||||
configurationType?: boolean | number
|
||||
fieldMetadataId?: boolean | number
|
||||
fieldDisplayMode?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
@@ -8890,6 +8898,12 @@ export const enumBarChartLayout = {
|
||||
HORIZONTAL: 'HORIZONTAL' as const
|
||||
}
|
||||
|
||||
export const enumFieldDisplayMode = {
|
||||
CARD: 'CARD' as const,
|
||||
FIELD: 'FIELD' as const,
|
||||
VIEW: 'VIEW' as const
|
||||
}
|
||||
|
||||
export const enumPageLayoutType = {
|
||||
RECORD_INDEX: 'RECORD_INDEX' as const,
|
||||
RECORD_PAGE: 'RECORD_PAGE' as const,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+138
@@ -0,0 +1,138 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-field-widgets',
|
||||
description:
|
||||
'Backfill FIELD widgets for standard object relation fields in existing page layouts',
|
||||
})
|
||||
export class BackfillFieldWidgetsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
shouldIncludeRecordPageLayouts: true,
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const standardFieldWidgets = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
widget.configuration?.configurationType ===
|
||||
WidgetConfigurationType.FIELD,
|
||||
);
|
||||
|
||||
if (standardFieldWidgets.length === 0) {
|
||||
this.logger.log(
|
||||
`No FIELD widgets found in standard configs for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { flatPageLayoutWidgetMaps: existingWidgetMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutWidgetMaps',
|
||||
]);
|
||||
|
||||
const widgetsToCreate = standardFieldWidgets.filter(
|
||||
(widget) =>
|
||||
!isDefined(
|
||||
existingWidgetMaps.byUniversalIdentifier[widget.universalIdentifier],
|
||||
),
|
||||
);
|
||||
|
||||
if (widgetsToCreate.length === 0) {
|
||||
this.logger.log(
|
||||
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${widgetsToCreate.length} FIELD widget(s) to create for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create ${widgetsToCreate.length} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: widgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create FIELD widgets:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created ${widgetsToCreate.length} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
|
||||
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
|
||||
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
|
||||
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
|
||||
@@ -58,6 +59,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
IdentifyObjectPermissionMetadataCommand,
|
||||
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
BackfillCommandMenuItemsCommand,
|
||||
BackfillFieldWidgetsCommand,
|
||||
BackfillNavigationMenuItemTypeCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
BackfillSelectFieldOptionIdsCommand,
|
||||
@@ -73,6 +75,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
IdentifyObjectPermissionMetadataCommand,
|
||||
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
BackfillCommandMenuItemsCommand,
|
||||
BackfillFieldWidgetsCommand,
|
||||
BackfillNavigationMenuItemTypeCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
BackfillSelectFieldOptionIdsCommand,
|
||||
|
||||
+3
@@ -34,6 +34,7 @@ import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgra
|
||||
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
|
||||
import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command';
|
||||
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
|
||||
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
|
||||
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
|
||||
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
|
||||
@@ -107,6 +108,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
|
||||
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
|
||||
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
|
||||
protected readonly backfillFieldWidgetsCommand: BackfillFieldWidgetsCommand,
|
||||
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
|
||||
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
|
||||
) {
|
||||
@@ -169,6 +171,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.backfillPageLayoutsCommand,
|
||||
this.seedCliApplicationRegistrationCommand,
|
||||
this.migrateMessagingInfrastructureToMetadataCommand,
|
||||
this.backfillFieldWidgetsCommand,
|
||||
this.backfillSelectFieldOptionIdsCommand,
|
||||
this.updateStandardIndexViewNamesCommand,
|
||||
];
|
||||
|
||||
+39
-1
@@ -2,9 +2,9 @@ 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';
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util';
|
||||
@@ -26,6 +27,7 @@ import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/met
|
||||
import { fromUpdateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util';
|
||||
import { throwOnFieldInputTranspilationsError } from 'src/engine/metadata-modules/flat-field-metadata/utils/throw-on-field-input-transpilations-error.util';
|
||||
import { computeFlatViewFieldsFromFieldsWidgets } from 'src/engine/metadata-modules/flat-view-field/utils/compute-flat-view-fields-from-fields-widgets.util';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { EMPTY_ORCHESTRATOR_FAILURE_REPORT } from 'src/engine/workspace-manager/workspace-migration/constant/empty-orchestrator-failure-report.constant';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
@@ -93,6 +95,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
flatIndexMaps: existingFlatIndexMaps,
|
||||
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
@@ -100,6 +103,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
'flatObjectMetadataMaps',
|
||||
'flatIndexMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -122,6 +126,31 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
},
|
||||
);
|
||||
|
||||
const deletedFieldIds = new Set(
|
||||
flatFieldMetadatasToDelete
|
||||
.map((f) => {
|
||||
const resolved = findFlatEntityByUniversalIdentifier({
|
||||
universalIdentifier: f.universalIdentifier,
|
||||
flatEntityMaps: existingFlatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
return resolved?.id;
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const flatPageLayoutWidgetsToDelete = Object.values(
|
||||
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
!isDefined(widget.deletedAt) &&
|
||||
widget.configuration?.configurationType ===
|
||||
WidgetConfigurationType.FIELD &&
|
||||
deletedFieldIds.has(widget.configuration.fieldMetadataId),
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
@@ -136,6 +165,15 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
flatEntityToDelete: flatIndexesToDelete,
|
||||
flatEntityToUpdate: flatIndexesToUpdate,
|
||||
},
|
||||
...(flatPageLayoutWidgetsToDelete.length > 0
|
||||
? {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: flatPageLayoutWidgetsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild,
|
||||
|
||||
+4
-8
@@ -51,10 +51,8 @@ export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
msg`Widget type VIEW is not supported yet.`,
|
||||
),
|
||||
IFRAME: validateIframeFlatPageLayoutWidgetForCreation,
|
||||
FIELD: rejectWidgetType(
|
||||
WidgetType.FIELD,
|
||||
'Widget type FIELD is not supported yet.',
|
||||
msg`Widget type FIELD is not supported yet.`,
|
||||
FIELD: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.FIELD,
|
||||
),
|
||||
FIELDS: validateFieldsFlatPageLayoutWidgetForCreation,
|
||||
GRAPH: validateGraphFlatPageLayoutWidgetForCreation,
|
||||
@@ -101,10 +99,8 @@ export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
msg`Widget type VIEW is not supported yet.`,
|
||||
),
|
||||
IFRAME: validateIframeFlatPageLayoutWidgetForUpdate,
|
||||
FIELD: rejectWidgetType(
|
||||
WidgetType.FIELD,
|
||||
'Widget type FIELD is not supported yet.',
|
||||
msg`Widget type FIELD is not supported yet.`,
|
||||
FIELD: validateSimpleRecordPageWidgetForUpdate(
|
||||
WidgetConfigurationType.FIELD,
|
||||
),
|
||||
FIELDS: () => [],
|
||||
GRAPH: validateGraphFlatPageLayoutWidgetForUpdate,
|
||||
|
||||
+18
-1
@@ -321,8 +321,25 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.FIELD: {
|
||||
const { fieldMetadataId, fieldDisplayMode, configurationType } =
|
||||
configuration;
|
||||
|
||||
const fieldMetadataUniversalIdentifier =
|
||||
getFieldMetadataUniversalIdentifier({
|
||||
fieldMetadataId,
|
||||
fieldMetadataUniversalIdentifierById,
|
||||
shouldThrowOnMissingIdentifier,
|
||||
});
|
||||
|
||||
return {
|
||||
configurationType,
|
||||
fieldMetadataId: fieldMetadataUniversalIdentifier ?? fieldMetadataId,
|
||||
fieldDisplayMode,
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.VIEW:
|
||||
case WidgetConfigurationType.FIELD:
|
||||
case WidgetConfigurationType.TIMELINE:
|
||||
case WidgetConfigurationType.TASKS:
|
||||
case WidgetConfigurationType.NOTES:
|
||||
|
||||
+13
-1
@@ -1,8 +1,10 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsIn, IsNotEmpty } from 'class-validator';
|
||||
import { IsEnum, IsIn, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
import { type FieldConfiguration } from 'twenty-shared/types';
|
||||
|
||||
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
@ObjectType('FieldConfiguration')
|
||||
@@ -11,4 +13,14 @@ export class FieldConfigurationDTO implements FieldConfiguration {
|
||||
@IsIn([WidgetConfigurationType.FIELD])
|
||||
@IsNotEmpty()
|
||||
configurationType: WidgetConfigurationType.FIELD;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
fieldMetadataId: string;
|
||||
|
||||
@Field(() => FieldDisplayMode)
|
||||
@IsEnum(FieldDisplayMode)
|
||||
@IsNotEmpty()
|
||||
fieldDisplayMode: FieldDisplayMode;
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum FieldDisplayMode {
|
||||
CARD = 'CARD',
|
||||
FIELD = 'FIELD',
|
||||
VIEW = 'VIEW',
|
||||
}
|
||||
|
||||
registerEnumType(FieldDisplayMode, {
|
||||
name: 'FieldDisplayMode',
|
||||
description: 'Display mode for field configuration widgets',
|
||||
});
|
||||
+13
-1
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
PageLayoutTabLayoutMode,
|
||||
type GridPosition,
|
||||
type PageLayoutWidgetCanvasPosition,
|
||||
type PageLayoutWidgetConditionalDisplay,
|
||||
type PageLayoutWidgetGridPosition,
|
||||
type PageLayoutWidgetVerticalListPosition,
|
||||
type GridPosition,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
@@ -67,6 +67,18 @@ export const VERTICAL_LIST_LAYOUT_POSITIONS = {
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
index: 0,
|
||||
},
|
||||
SECOND: {
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
index: 1,
|
||||
},
|
||||
THIRD: {
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
index: 2,
|
||||
},
|
||||
FOURTH: {
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
index: 3,
|
||||
},
|
||||
} as const satisfies Record<string, PageLayoutWidgetVerticalListPosition>;
|
||||
|
||||
export const CANVAS_LAYOUT_POSITIONS = {
|
||||
|
||||
+107
-80
@@ -6,58 +6,67 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
"id": "00000000-0000-0000-0000-000000000011",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000024",
|
||||
"id": "00000000-0000-0000-0000-000000000027",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000025",
|
||||
"id": "00000000-0000-0000-0000-000000000028",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000022",
|
||||
"id": "00000000-0000-0000-0000-000000000025",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000023",
|
||||
"id": "00000000-0000-0000-0000-000000000026",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000020",
|
||||
"id": "00000000-0000-0000-0000-000000000023",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000021",
|
||||
"id": "00000000-0000-0000-0000-000000000024",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000012",
|
||||
"widgets": {
|
||||
"accountOwner": {
|
||||
"id": "00000000-0000-0000-0000-000000000015",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000013",
|
||||
},
|
||||
"opportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000016",
|
||||
},
|
||||
"people": {
|
||||
"id": "00000000-0000-0000-0000-000000000014",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000018",
|
||||
"id": "00000000-0000-0000-0000-000000000021",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000019",
|
||||
"id": "00000000-0000-0000-0000-000000000022",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000016",
|
||||
"id": "00000000-0000-0000-0000-000000000019",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000017",
|
||||
"id": "00000000-0000-0000-0000-000000000020",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000014",
|
||||
"id": "00000000-0000-0000-0000-000000000017",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000015",
|
||||
"id": "00000000-0000-0000-0000-000000000018",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -98,257 +107,275 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
},
|
||||
},
|
||||
"noteRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000056",
|
||||
"id": "00000000-0000-0000-0000-000000000064",
|
||||
"tabs": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000064",
|
||||
"id": "00000000-0000-0000-0000-000000000072",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000065",
|
||||
"id": "00000000-0000-0000-0000-000000000073",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000057",
|
||||
"id": "00000000-0000-0000-0000-000000000065",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000058",
|
||||
"id": "00000000-0000-0000-0000-000000000066",
|
||||
},
|
||||
"noteRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000059",
|
||||
"id": "00000000-0000-0000-0000-000000000067",
|
||||
},
|
||||
},
|
||||
},
|
||||
"note": {
|
||||
"id": "00000000-0000-0000-0000-000000000060",
|
||||
"id": "00000000-0000-0000-0000-000000000068",
|
||||
"widgets": {
|
||||
"noteRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000061",
|
||||
"id": "00000000-0000-0000-0000-000000000069",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000062",
|
||||
"id": "00000000-0000-0000-0000-000000000070",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000063",
|
||||
"id": "00000000-0000-0000-0000-000000000071",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"opportunityRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000041",
|
||||
"id": "00000000-0000-0000-0000-000000000046",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000054",
|
||||
"id": "00000000-0000-0000-0000-000000000062",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000055",
|
||||
"id": "00000000-0000-0000-0000-000000000063",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000052",
|
||||
"id": "00000000-0000-0000-0000-000000000060",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000053",
|
||||
"id": "00000000-0000-0000-0000-000000000061",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000050",
|
||||
"id": "00000000-0000-0000-0000-000000000058",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000051",
|
||||
"id": "00000000-0000-0000-0000-000000000059",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000042",
|
||||
"id": "00000000-0000-0000-0000-000000000047",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000043",
|
||||
"company": {
|
||||
"id": "00000000-0000-0000-0000-000000000050",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000048",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000048",
|
||||
},
|
||||
"owner": {
|
||||
"id": "00000000-0000-0000-0000-000000000051",
|
||||
},
|
||||
"pointOfContact": {
|
||||
"id": "00000000-0000-0000-0000-000000000049",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000056",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000057",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000046",
|
||||
"id": "00000000-0000-0000-0000-000000000054",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000047",
|
||||
"id": "00000000-0000-0000-0000-000000000055",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000044",
|
||||
"id": "00000000-0000-0000-0000-000000000052",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000045",
|
||||
"id": "00000000-0000-0000-0000-000000000053",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"personRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000026",
|
||||
"id": "00000000-0000-0000-0000-000000000029",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000039",
|
||||
"id": "00000000-0000-0000-0000-000000000044",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000040",
|
||||
"id": "00000000-0000-0000-0000-000000000045",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000037",
|
||||
"id": "00000000-0000-0000-0000-000000000042",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000038",
|
||||
"id": "00000000-0000-0000-0000-000000000043",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000035",
|
||||
"id": "00000000-0000-0000-0000-000000000040",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000036",
|
||||
"id": "00000000-0000-0000-0000-000000000041",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000027",
|
||||
"id": "00000000-0000-0000-0000-000000000030",
|
||||
"widgets": {
|
||||
"company": {
|
||||
"id": "00000000-0000-0000-0000-000000000032",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000028",
|
||||
"id": "00000000-0000-0000-0000-000000000031",
|
||||
},
|
||||
"pointOfContactForOpportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000033",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000033",
|
||||
"id": "00000000-0000-0000-0000-000000000038",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000034",
|
||||
"id": "00000000-0000-0000-0000-000000000039",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000031",
|
||||
"id": "00000000-0000-0000-0000-000000000036",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000032",
|
||||
"id": "00000000-0000-0000-0000-000000000037",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000029",
|
||||
"id": "00000000-0000-0000-0000-000000000034",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000030",
|
||||
"id": "00000000-0000-0000-0000-000000000035",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"taskRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000066",
|
||||
"id": "00000000-0000-0000-0000-000000000074",
|
||||
"tabs": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000074",
|
||||
"id": "00000000-0000-0000-0000-000000000083",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000075",
|
||||
"id": "00000000-0000-0000-0000-000000000084",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000067",
|
||||
"id": "00000000-0000-0000-0000-000000000075",
|
||||
"widgets": {
|
||||
"assignee": {
|
||||
"id": "00000000-0000-0000-0000-000000000078",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000068",
|
||||
"id": "00000000-0000-0000-0000-000000000076",
|
||||
},
|
||||
"taskRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000069",
|
||||
"id": "00000000-0000-0000-0000-000000000077",
|
||||
},
|
||||
},
|
||||
},
|
||||
"note": {
|
||||
"id": "00000000-0000-0000-0000-000000000070",
|
||||
"id": "00000000-0000-0000-0000-000000000079",
|
||||
"widgets": {
|
||||
"taskRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000071",
|
||||
"id": "00000000-0000-0000-0000-000000000080",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000072",
|
||||
"id": "00000000-0000-0000-0000-000000000081",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000073",
|
||||
"id": "00000000-0000-0000-0000-000000000082",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000076",
|
||||
"id": "00000000-0000-0000-0000-000000000085",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000077",
|
||||
"id": "00000000-0000-0000-0000-000000000086",
|
||||
"widgets": {
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000078",
|
||||
"id": "00000000-0000-0000-0000-000000000087",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowRunRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000084",
|
||||
"id": "00000000-0000-0000-0000-000000000093",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000087",
|
||||
"id": "00000000-0000-0000-0000-000000000096",
|
||||
"widgets": {
|
||||
"workflowRun": {
|
||||
"id": "00000000-0000-0000-0000-000000000088",
|
||||
"id": "00000000-0000-0000-0000-000000000097",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000085",
|
||||
"id": "00000000-0000-0000-0000-000000000094",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000086",
|
||||
"id": "00000000-0000-0000-0000-000000000095",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowVersionRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000079",
|
||||
"id": "00000000-0000-0000-0000-000000000088",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000082",
|
||||
"id": "00000000-0000-0000-0000-000000000091",
|
||||
"widgets": {
|
||||
"workflowVersion": {
|
||||
"id": "00000000-0000-0000-0000-000000000083",
|
||||
"id": "00000000-0000-0000-0000-000000000092",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000080",
|
||||
"id": "00000000-0000-0000-0000-000000000089",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000081",
|
||||
"id": "00000000-0000-0000-0000-000000000090",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+30
@@ -1,8 +1,11 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
TAB_PROPS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import {
|
||||
@@ -19,6 +22,33 @@ const COMPANY_PAGE_TABS = {
|
||||
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0111',
|
||||
...WIDGET_PROPS.fields,
|
||||
},
|
||||
people: {
|
||||
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0112',
|
||||
title: 'People',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.company.fields.people.universalIdentifier,
|
||||
},
|
||||
accountOwner: {
|
||||
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0113',
|
||||
title: 'Account Owner',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.company.fields.accountOwner.universalIdentifier,
|
||||
},
|
||||
opportunities: {
|
||||
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0114',
|
||||
title: 'Opportunities',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.FOURTH,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.company.fields.opportunities.universalIdentifier,
|
||||
},
|
||||
},
|
||||
},
|
||||
timeline: {
|
||||
|
||||
+31
@@ -1,8 +1,11 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
TAB_PROPS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import {
|
||||
@@ -19,6 +22,34 @@ const OPPORTUNITY_PAGE_TABS = {
|
||||
universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1311',
|
||||
...WIDGET_PROPS.fields,
|
||||
},
|
||||
pointOfContact: {
|
||||
universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1312',
|
||||
title: 'Point of Contact',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.pointOfContact
|
||||
.universalIdentifier,
|
||||
},
|
||||
company: {
|
||||
universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1313',
|
||||
title: 'Company',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.company.universalIdentifier,
|
||||
},
|
||||
owner: {
|
||||
universalIdentifier: '20202020-ac03-4003-8003-0aa0b1ca1314',
|
||||
title: 'Owner',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.FOURTH,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.owner.universalIdentifier,
|
||||
},
|
||||
},
|
||||
},
|
||||
timeline: {
|
||||
|
||||
+3
-1
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type GridPosition,
|
||||
type PageLayoutTabLayoutMode,
|
||||
type PageLayoutWidgetConditionalDisplay,
|
||||
type PageLayoutWidgetPosition,
|
||||
type GridPosition,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
@@ -15,6 +15,7 @@ export type StandardPageLayoutWidgetConfig = {
|
||||
gridPosition?: GridPosition;
|
||||
position?: PageLayoutWidgetPosition;
|
||||
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
|
||||
fieldUniversalIdentifier?: string;
|
||||
};
|
||||
|
||||
export type StandardPageLayoutTabConfig = {
|
||||
@@ -42,6 +43,7 @@ export type StandardRecordPageWidgetConfig = {
|
||||
gridPosition: GridPosition;
|
||||
position?: PageLayoutWidgetPosition;
|
||||
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
|
||||
fieldUniversalIdentifier?: string;
|
||||
};
|
||||
|
||||
export type StandardRecordPageTabConfig = {
|
||||
|
||||
+22
@@ -1,8 +1,11 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
TAB_PROPS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import {
|
||||
@@ -19,6 +22,25 @@ const PERSON_PAGE_TABS = {
|
||||
universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11211',
|
||||
...WIDGET_PROPS.fields,
|
||||
},
|
||||
company: {
|
||||
universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11212',
|
||||
title: 'Company',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.person.fields.company.universalIdentifier,
|
||||
},
|
||||
pointOfContactForOpportunities: {
|
||||
universalIdentifier: '20202020-ac02-4002-8002-ae0a1ea11213',
|
||||
title: 'Opportunities',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.person.fields.pointOfContactForOpportunities
|
||||
.universalIdentifier,
|
||||
},
|
||||
},
|
||||
},
|
||||
timeline: {
|
||||
|
||||
+12
@@ -1,10 +1,13 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
|
||||
CONDITIONAL_DISPLAY_DEVICE_MOBILE,
|
||||
GRID_POSITIONS,
|
||||
TAB_PROPS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import {
|
||||
@@ -29,6 +32,15 @@ const TASK_PAGE_TABS = {
|
||||
position: { layoutMode: TAB_PROPS.home.layoutMode, index: 1 },
|
||||
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_MOBILE,
|
||||
},
|
||||
assignee: {
|
||||
universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5513',
|
||||
title: 'Assignee',
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
|
||||
fieldUniversalIdentifier:
|
||||
STANDARD_OBJECTS.task.fields.assignee.universalIdentifier,
|
||||
},
|
||||
},
|
||||
},
|
||||
note: {
|
||||
|
||||
+57
@@ -5,6 +5,7 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
|
||||
@@ -77,10 +78,12 @@ const buildRecordPageWidgetConfigurations = ({
|
||||
widgetType,
|
||||
layoutObjectName,
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
fieldUniversalIdentifier,
|
||||
}: {
|
||||
widgetType: WidgetType;
|
||||
layoutObjectName: AllStandardObjectName | null;
|
||||
standardObjectMetadataRelatedEntityIds: BuildStandardFlatPageLayoutWidgetMetadataMapsArgs['standardObjectMetadataRelatedEntityIds'];
|
||||
fieldUniversalIdentifier?: string;
|
||||
}): {
|
||||
configuration: AllPageLayoutWidgetConfiguration;
|
||||
universalConfiguration: CreateStandardPageLayoutWidgetContext['universalConfiguration'];
|
||||
@@ -92,6 +95,18 @@ const buildRecordPageWidgetConfigurations = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
widgetType === WidgetType.FIELD &&
|
||||
isDefined(layoutObjectName) &&
|
||||
isDefined(fieldUniversalIdentifier)
|
||||
) {
|
||||
return buildFieldWidgetConfiguration({
|
||||
objectName: layoutObjectName,
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
fieldUniversalIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
const configurationType = WIDGET_TYPE_TO_CONFIGURATION_TYPE[widgetType];
|
||||
|
||||
if (!configurationType) {
|
||||
@@ -175,6 +190,47 @@ const buildFieldsWidgetConfiguration = ({
|
||||
};
|
||||
};
|
||||
|
||||
const buildFieldWidgetConfiguration = ({
|
||||
objectName,
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
fieldUniversalIdentifier,
|
||||
}: {
|
||||
objectName: AllStandardObjectName;
|
||||
standardObjectMetadataRelatedEntityIds: BuildStandardFlatPageLayoutWidgetMetadataMapsArgs['standardObjectMetadataRelatedEntityIds'];
|
||||
fieldUniversalIdentifier: string;
|
||||
}): {
|
||||
configuration: AllPageLayoutWidgetConfiguration;
|
||||
universalConfiguration: CreateStandardPageLayoutWidgetContext['universalConfiguration'];
|
||||
} => {
|
||||
const fields = standardObjectMetadataRelatedEntityIds[objectName]
|
||||
.fields as Record<string, { id: string }>;
|
||||
|
||||
const fieldName = Object.keys(STANDARD_OBJECTS[objectName].fields).find(
|
||||
(name) =>
|
||||
(
|
||||
STANDARD_OBJECTS[objectName].fields as Record<
|
||||
string,
|
||||
{ universalIdentifier: string }
|
||||
>
|
||||
)[name]?.universalIdentifier === fieldUniversalIdentifier,
|
||||
);
|
||||
|
||||
const fieldMetadataId = fieldName ? (fields[fieldName]?.id ?? null) : null;
|
||||
|
||||
return {
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: fieldMetadataId ?? fieldUniversalIdentifier,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
universalConfiguration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: fieldUniversalIdentifier,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const computeRecordPageWidgets = ({
|
||||
now,
|
||||
workspaceId,
|
||||
@@ -225,6 +281,7 @@ const computeRecordPageWidgets = ({
|
||||
widgetType: widget.type,
|
||||
layoutObjectName,
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
fieldUniversalIdentifier: widget.fieldUniversalIdentifier,
|
||||
});
|
||||
|
||||
allWidgets.push(
|
||||
|
||||
+12
-1
@@ -311,8 +311,19 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
|
||||
};
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.FIELD: {
|
||||
const { fieldMetadataId: fieldMetadataUniversalIdentifier, ...rest } =
|
||||
universalConfiguration;
|
||||
|
||||
const fieldMetadataId = resolveFieldMetadataIdOrThrow({
|
||||
fieldMetadataUniversalIdentifier,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
return { ...rest, fieldMetadataId };
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.VIEW:
|
||||
case WidgetConfigurationType.FIELD:
|
||||
case WidgetConfigurationType.TIMELINE:
|
||||
case WidgetConfigurationType.TASKS:
|
||||
case WidgetConfigurationType.NOTES:
|
||||
|
||||
@@ -93,6 +93,8 @@ export type ViewConfiguration = {
|
||||
|
||||
export type FieldConfiguration = {
|
||||
configurationType: 'FIELD';
|
||||
fieldMetadataId: string;
|
||||
fieldDisplayMode: 'CARD' | 'FIELD' | 'VIEW';
|
||||
};
|
||||
|
||||
export type FieldsConfiguration = {
|
||||
|
||||
Reference in New Issue
Block a user