Replace newFieldDefaultConfiguration with newFieldDefaultVisibility (#18539)

https://github.com/user-attachments/assets/365092cb-0fe1-44f7-9ae6-c6fc5edb98b2

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Baptiste Devessier
2026-03-11 13:14:30 +01:00
committed by GitHub
parent e4e7137660
commit ab5fb1f658
24 changed files with 843 additions and 1096 deletions
File diff suppressed because one or more lines are too long
@@ -167,11 +167,8 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
... on FieldsConfiguration {
configurationType
viewId
newFieldDefaultVisibility
shouldAllowUserToSeeHiddenFields
newFieldDefaultConfiguration {
isVisible
viewFieldGroupId
}
}
... on FilesConfiguration {
configurationType
@@ -11,18 +11,15 @@ import { fieldsWidgetGroupsDraftComponentState } from '@/page-layout/states/fiel
import { fieldsWidgetUngroupedFieldsDraftComponentState } from '@/page-layout/states/fieldsWidgetUngroupedFieldsDraftComponentState';
import { FieldsConfigurationGroupEditor } from '@/page-layout/widgets/fields/components/FieldsConfigurationGroupEditor';
import { FieldsConfigurationUngroupedEditor } from '@/page-layout/widgets/fields/components/FieldsConfigurationUngroupedEditor';
import { NEW_FIELDS_INDICATOR_DRAGGABLE_ID } from '@/page-layout/widgets/fields/constants/NewFieldsIndicatorDraggableId';
import { useCreateFieldsWidgetEditorGroup } from '@/page-layout/widgets/fields/hooks/useCreateFieldsWidgetEditorGroup';
import { useDeleteFieldsWidgetEditorGroup } from '@/page-layout/widgets/fields/hooks/useDeleteFieldsWidgetEditorGroup';
import { useFieldsWidgetEditorMode } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetEditorMode';
import { useGetNewFieldDefaultConfiguration } from '@/page-layout/widgets/fields/hooks/useGetNewFieldDefaultConfiguration';
import { useMoveFieldInDraft } from '@/page-layout/widgets/fields/hooks/useMoveFieldInDraft';
import { useMoveUngroupedFieldInDraft } from '@/page-layout/widgets/fields/hooks/useMoveUngroupedFieldInDraft';
import { useReorderFieldsWidgetEditorGroups } from '@/page-layout/widgets/fields/hooks/useReorderFieldsWidgetEditorGroups';
import { useToggleFieldVisibilityInDraft } from '@/page-layout/widgets/fields/hooks/useToggleFieldVisibilityInDraft';
import { useToggleUngroupedFieldVisibilityInDraft } from '@/page-layout/widgets/fields/hooks/useToggleUngroupedFieldVisibilityInDraft';
import { useUpdateFieldsWidgetEditorGroup } from '@/page-layout/widgets/fields/hooks/useUpdateFieldsWidgetEditorGroup';
import { useUpdateNewFieldDefaultConfiguration } from '@/page-layout/widgets/fields/hooks/useUpdateNewFieldDefaultConfiguration';
import { getFieldsConfigurationGroupRenameDropdownId } from '@/page-layout/widgets/fields/utils/getFieldsConfigurationGroupRenameDropdownId';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
@@ -107,17 +104,6 @@ export const FieldsConfigurationEditor = ({
widgetId,
});
const { newFieldDefaultConfiguration } = useGetNewFieldDefaultConfiguration({
pageLayoutId,
widgetId,
});
const { updateNewFieldDefaultConfiguration } =
useUpdateNewFieldDefaultConfiguration({
pageLayoutId,
widgetId,
});
const { openDropdown } = useOpenDropdown();
const [renamingGroupValue, setRenamingGroupValue] = useState('');
@@ -141,7 +127,7 @@ export const FieldsConfigurationEditor = ({
};
const handleDragEnd = (result: DropResult) => {
const { source, destination, type, draggableId } = result;
const { source, destination, type } = result;
if (!destination) {
return;
@@ -154,17 +140,6 @@ export const FieldsConfigurationEditor = ({
return;
}
if (draggableId === NEW_FIELDS_INDICATOR_DRAGGABLE_ID) {
const cleanDestinationGroupId = destination.droppableId.replace(
'group-',
'',
);
updateNewFieldDefaultConfiguration({
viewFieldGroupId: cleanDestinationGroupId,
});
return;
}
if (type === 'GROUP') {
handleGroupReorder(source.index, destination.index);
} else if (type === 'FIELD') {
@@ -227,12 +202,6 @@ export const FieldsConfigurationEditor = ({
onMoveField={moveUngroupedField}
onToggleFieldVisibility={toggleUngroupedFieldVisibility}
onAddGroup={() => handleAddGroup({})}
newFieldsIsVisible={newFieldDefaultConfiguration.isVisible}
onToggleNewFieldsVisibility={() =>
updateNewFieldDefaultConfiguration({
isVisible: !newFieldDefaultConfiguration.isVisible,
})
}
/>
);
}
@@ -276,15 +245,6 @@ export const FieldsConfigurationEditor = ({
renamingGroupValue={renamingGroupValue}
onRenamingGroupValueChange={setRenamingGroupValue}
onStartRename={handleStartRename}
showNewFieldsItem={
group.id === newFieldDefaultConfiguration.viewFieldGroupId
}
newFieldsIsVisible={newFieldDefaultConfiguration.isVisible}
onToggleNewFieldsVisibility={() =>
updateNewFieldDefaultConfiguration({
isVisible: !newFieldDefaultConfiguration.isVisible,
})
}
/>
)}
</Draggable>
@@ -8,7 +8,6 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
import { FieldsConfigurationFieldEditor } from '@/page-layout/widgets/fields/components/FieldsConfigurationFieldEditor';
import { FieldsConfigurationGroupDropdown } from '@/page-layout/widgets/fields/components/FieldsConfigurationGroupDropdown';
import { FieldsConfigurationGroupRenameInput } from '@/page-layout/widgets/fields/components/FieldsConfigurationGroupRenameInput';
import { NEW_FIELDS_INDICATOR_DRAGGABLE_ID } from '@/page-layout/widgets/fields/constants/NewFieldsIndicatorDraggableId';
import { type FieldsWidgetGroup } from '@/page-layout/widgets/fields/types/FieldsWidgetGroup';
import { getFieldsConfigurationGroupRenameDropdownId } from '@/page-layout/widgets/fields/utils/getFieldsConfigurationGroupRenameDropdownId';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -16,13 +15,8 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
import {
IconEye,
IconEyeOff,
IconNewSection,
IconPlaylistAdd,
} from 'twenty-ui/display';
import { MenuItem, MenuItemDraggable } from 'twenty-ui/navigation';
import { IconNewSection } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { FieldsConfigurationGroupDraggableHeader } from '@/page-layout/widgets/fields/components/FieldsConfigurationGroupDraggableHeader';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -89,9 +83,6 @@ type FieldsConfigurationGroupEditorProps = {
renamingGroupValue: string;
onRenamingGroupValueChange: (value: string) => void;
onStartRename: (params: { groupId: string; groupName: string }) => void;
showNewFieldsItem: boolean;
newFieldsIsVisible: boolean;
onToggleNewFieldsVisibility: () => void;
};
export const FieldsConfigurationGroupEditor = ({
@@ -105,9 +96,6 @@ export const FieldsConfigurationGroupEditor = ({
renamingGroupValue,
onRenamingGroupValueChange,
onStartRename,
showNewFieldsItem,
newFieldsIsVisible,
onToggleNewFieldsVisibility,
}: FieldsConfigurationGroupEditorProps) => {
const { t } = useLingui();
@@ -223,33 +211,6 @@ export const FieldsConfigurationGroupEditor = ({
/>
);
})}
{showNewFieldsItem && (
<DraggableItem
key={NEW_FIELDS_INDICATOR_DRAGGABLE_ID}
draggableId={NEW_FIELDS_INDICATOR_DRAGGABLE_ID}
index={sortedFields.length}
isInsideScrollableContainer
itemComponent={
<MenuItemDraggable
LeftIcon={IconPlaylistAdd}
text={t`New fields`}
contextualText={t`Default position/visibility for fields created in the future`}
gripMode="onHover"
withIconContainer
isIconDisplayedOnHoverOnly={false}
iconButtons={[
{
Icon: newFieldsIsVisible ? IconEye : IconEyeOff,
onClick: (e) => {
e.stopPropagation();
onToggleNewFieldsVisibility();
},
},
]}
/>
}
/>
)}
{droppableProvided.placeholder}
</StyledFieldsDroppable>
)}
@@ -6,13 +6,8 @@ import { FieldsConfigurationFieldEditor } from '@/page-layout/widgets/fields/com
import { type FieldsWidgetGroupField } from '@/page-layout/widgets/fields/types/FieldsWidgetGroup';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import {
IconEye,
IconEyeOff,
IconNewSection,
IconPlaylistAdd,
} from 'twenty-ui/display';
import { MenuItem, MenuItemDraggable } from 'twenty-ui/navigation';
import { IconNewSection } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
const StyledFieldsDroppable = styled.div`
display: flex;
@@ -25,8 +20,6 @@ type FieldsConfigurationUngroupedEditorProps = {
onMoveField: (sourceIndex: number, destinationIndex: number) => void;
onToggleFieldVisibility: (fieldMetadataId: string) => void;
onAddGroup: () => void;
newFieldsIsVisible: boolean;
onToggleNewFieldsVisibility: () => void;
};
export const FieldsConfigurationUngroupedEditor = ({
@@ -34,8 +27,6 @@ export const FieldsConfigurationUngroupedEditor = ({
onMoveField,
onToggleFieldVisibility,
onAddGroup,
newFieldsIsVisible,
onToggleNewFieldsVisibility,
}: FieldsConfigurationUngroupedEditorProps) => {
const { t } = useLingui();
@@ -89,25 +80,6 @@ export const FieldsConfigurationUngroupedEditor = ({
))}
{provided.placeholder}
<MenuItemDraggable
LeftIcon={IconPlaylistAdd}
text={t`New fields`}
contextualText={t`Default position/visibility for fields created in the future`}
gripMode="never"
isDragDisabled
withIconContainer
isIconDisplayedOnHoverOnly={false}
iconButtons={[
{
Icon: newFieldsIsVisible ? IconEye : IconEyeOff,
onClick: (e) => {
e.stopPropagation();
onToggleNewFieldsVisibility();
},
},
]}
/>
<MenuItem
LeftIcon={IconNewSection}
withIconContainer
@@ -1 +0,0 @@
export const NEW_FIELDS_INDICATOR_DRAGGABLE_ID = 'new-fields-indicator';
@@ -1,52 +0,0 @@
import { fieldsWidgetGroupsDraftComponentState } from '@/page-layout/states/fieldsWidgetGroupsDraftComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { getLastGroupId } from '@/page-layout/widgets/fields/utils/getLastGroupId';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { isDefined } from 'twenty-shared/utils';
import {
type FieldsConfiguration,
WidgetConfigurationType,
} from '~/generated-metadata/graphql';
type UseGetNewFieldDefaultConfigurationParams = {
pageLayoutId: string;
widgetId: string;
};
export const useGetNewFieldDefaultConfiguration = ({
pageLayoutId,
widgetId,
}: UseGetNewFieldDefaultConfigurationParams) => {
const pageLayoutDraft = useAtomComponentStateValue(
pageLayoutDraftComponentState,
pageLayoutId,
);
const fieldsWidgetGroupsDraft = useAtomComponentStateValue(
fieldsWidgetGroupsDraftComponentState,
pageLayoutId,
);
const draftGroups = fieldsWidgetGroupsDraft[widgetId] ?? [];
const widget = pageLayoutDraft.tabs
.flatMap((tab) => tab.widgets)
.find((w) => w.id === widgetId);
const fieldsConfiguration =
isDefined(widget?.configuration) &&
widget.configuration.configurationType === WidgetConfigurationType.FIELDS
? (widget.configuration as FieldsConfiguration)
: null;
const lastGroupId = getLastGroupId(draftGroups);
const persisted = fieldsConfiguration?.newFieldDefaultConfiguration;
const newFieldDefaultConfiguration = {
isVisible: persisted?.isVisible ?? true,
viewFieldGroupId: persisted?.viewFieldGroupId ?? lastGroupId,
};
return { newFieldDefaultConfiguration, fieldsConfiguration };
};
@@ -0,0 +1,37 @@
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { isDefined } from 'twenty-shared/utils';
import {
type FieldsConfiguration,
WidgetConfigurationType,
} from '~/generated-metadata/graphql';
type UseGetNewFieldDefaultVisibilityParams = {
pageLayoutId: string;
widgetId: string;
};
export const useGetNewFieldDefaultVisibility = ({
pageLayoutId,
widgetId,
}: UseGetNewFieldDefaultVisibilityParams) => {
const pageLayoutDraft = useAtomComponentStateValue(
pageLayoutDraftComponentState,
pageLayoutId,
);
const widget = pageLayoutDraft.tabs
.flatMap((tab) => tab.widgets)
.find((w) => w.id === widgetId);
const fieldsConfiguration =
isDefined(widget?.configuration) &&
widget.configuration.configurationType === WidgetConfigurationType.FIELDS
? (widget.configuration as FieldsConfiguration)
: null;
const newFieldDefaultVisibility =
fieldsConfiguration?.newFieldDefaultVisibility ?? true;
return { newFieldDefaultVisibility, fieldsConfiguration };
};
@@ -1,45 +0,0 @@
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
import { useGetNewFieldDefaultConfiguration } from '@/page-layout/widgets/fields/hooks/useGetNewFieldDefaultConfiguration';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
type UseUpdateNewFieldDefaultConfigurationParams = {
pageLayoutId: string;
widgetId: string;
};
export const useUpdateNewFieldDefaultConfiguration = ({
pageLayoutId,
widgetId,
}: UseUpdateNewFieldDefaultConfigurationParams) => {
const { newFieldDefaultConfiguration, fieldsConfiguration } =
useGetNewFieldDefaultConfiguration({ pageLayoutId, widgetId });
const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(pageLayoutId);
const updateNewFieldDefaultConfiguration = useCallback(
(updates: { isVisible?: boolean; viewFieldGroupId?: string | null }) => {
if (!isDefined(fieldsConfiguration)) {
return;
}
updatePageLayoutWidget(widgetId, {
configuration: {
...fieldsConfiguration,
newFieldDefaultConfiguration: {
...newFieldDefaultConfiguration,
...updates,
},
},
});
},
[
fieldsConfiguration,
newFieldDefaultConfiguration,
updatePageLayoutWidget,
widgetId,
],
);
return { updateNewFieldDefaultConfiguration };
};
@@ -0,0 +1,37 @@
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
import { useGetNewFieldDefaultVisibility } from '@/page-layout/widgets/fields/hooks/useGetNewFieldDefaultVisibility';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
type UseUpdateNewFieldDefaultVisibilityParams = {
pageLayoutId: string;
widgetId: string;
};
export const useUpdateNewFieldDefaultVisibility = ({
pageLayoutId,
widgetId,
}: UseUpdateNewFieldDefaultVisibilityParams) => {
const { newFieldDefaultVisibility, fieldsConfiguration } =
useGetNewFieldDefaultVisibility({ pageLayoutId, widgetId });
const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(pageLayoutId);
const updateNewFieldDefaultVisibility = useCallback(
(isVisible: boolean) => {
if (!isDefined(fieldsConfiguration)) {
return;
}
updatePageLayoutWidget(widgetId, {
configuration: {
...fieldsConfiguration,
newFieldDefaultVisibility: isVisible,
},
});
},
[fieldsConfiguration, updatePageLayoutWidget, widgetId],
);
return { updateNewFieldDefaultVisibility, newFieldDefaultVisibility };
};
@@ -1,46 +0,0 @@
import { type FieldsWidgetGroup } from '@/page-layout/widgets/fields/types/FieldsWidgetGroup';
import { getLastGroupId } from '@/page-layout/widgets/fields/utils/getLastGroupId';
const makeGroup = (
overrides: Partial<FieldsWidgetGroup> & { id: string },
): FieldsWidgetGroup => ({
name: 'Group',
position: 0,
isVisible: true,
fields: [],
...overrides,
});
describe('getLastGroupId', () => {
it('should return null for empty array', () => {
expect(getLastGroupId([])).toBeNull();
});
it('should return the id of a single group', () => {
const groups = [makeGroup({ id: 'g1', position: 0 })];
expect(getLastGroupId(groups)).toBe('g1');
});
it('should return the id of the group with the highest position', () => {
const groups = [
makeGroup({ id: 'g1', position: 0 }),
makeGroup({ id: 'g2', position: 2 }),
makeGroup({ id: 'g3', position: 1 }),
];
expect(getLastGroupId(groups)).toBe('g2');
});
it('should not mutate the original array', () => {
const groups = [
makeGroup({ id: 'g2', position: 2 }),
makeGroup({ id: 'g1', position: 0 }),
];
getLastGroupId(groups);
expect(groups[0].id).toBe('g2');
expect(groups[1].id).toBe('g1');
});
});
@@ -1,11 +0,0 @@
import { type FieldsWidgetGroup } from '@/page-layout/widgets/fields/types/FieldsWidgetGroup';
export const getLastGroupId = (groups: FieldsWidgetGroup[]): string | null => {
if (groups.length === 0) {
return null;
}
const sortedGroups = [...groups].sort((a, b) => a.position - b.position);
return sortedGroups[sortedGroups.length - 1].id;
};
@@ -0,0 +1,46 @@
import { CommandMenuItemToggle } from '@/command-menu/components/CommandMenuItemToggle';
import { useGetNewFieldDefaultVisibility } from '@/page-layout/widgets/fields/hooks/useGetNewFieldDefaultVisibility';
import { useUpdateNewFieldDefaultVisibility } from '@/page-layout/widgets/fields/hooks/useUpdateNewFieldDefaultVisibility';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useLingui } from '@lingui/react/macro';
import { IconEye } from 'twenty-ui/display';
export const NewFieldDefaultVisibilityToggle = ({
pageLayoutId,
widgetId,
}: {
pageLayoutId: string;
widgetId: string;
}) => {
const { t } = useLingui();
const { newFieldDefaultVisibility } = useGetNewFieldDefaultVisibility({
pageLayoutId,
widgetId,
});
const { updateNewFieldDefaultVisibility } =
useUpdateNewFieldDefaultVisibility({
pageLayoutId,
widgetId,
});
const handleToggle = () => {
updateNewFieldDefaultVisibility(!newFieldDefaultVisibility);
};
return (
<SelectableListItem
itemId="new-field-default-visibility"
onEnter={handleToggle}
>
<CommandMenuItemToggle
LeftIcon={IconEye}
text={t`Set fields created in the future as "visible"`}
id="new-field-default-visibility"
toggled={newFieldDefaultVisibility}
onToggleChange={handleToggle}
/>
</SelectableListItem>
);
};
@@ -3,6 +3,7 @@ import { CommandMenuItemToggle } from '@/command-menu/components/CommandMenuItem
import { useFieldsWidgetGroups } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetGroups';
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
import { SidePanelList } from '@/side-panel/components/SidePanelList';
import { NewFieldDefaultVisibilityToggle } from '@/side-panel/pages/page-layout/components/NewFieldDefaultVisibilityToggle';
import { WidgetSettingsFooter } from '@/side-panel/pages/page-layout/components/WidgetSettingsFooter';
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
import { usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord';
@@ -78,6 +79,7 @@ export const SidePanelPageLayoutFieldsSettings = () => {
const selectableItemIds = [
'layout',
'new-field-default-visibility',
'display-more-fields-button',
'action-button',
'move-down',
@@ -119,6 +121,10 @@ export const SidePanelPageLayoutFieldsSettings = () => {
onToggleChange={handleToggleShouldAllowUserToSeeHiddenFields}
/>
</SelectableListItem>
<NewFieldDefaultVisibilityToggle
pageLayoutId={pageLayoutId}
widgetId={widgetInEditMode.id}
/>
</SidePanelGroup>
</SidePanelList>
</StyledSidePanelContainer>
@@ -874,11 +874,6 @@ type RatioAggregateConfig {
optionValue: String!
}
type NewFieldDefaultConfiguration {
isVisible: Boolean!
viewFieldGroupId: String
}
type RichTextV2Body {
blocknote: String
markdown: String
@@ -1168,7 +1163,7 @@ type FieldRichTextConfiguration {
type FieldsConfiguration {
configurationType: WidgetConfigurationType!
viewId: String
newFieldDefaultConfiguration: NewFieldDefaultConfiguration
newFieldDefaultVisibility: Boolean
shouldAllowUserToSeeHiddenFields: Boolean
}
@@ -644,12 +644,6 @@ export interface RatioAggregateConfig {
__typename: 'RatioAggregateConfig'
}
export interface NewFieldDefaultConfiguration {
isVisible: Scalars['Boolean']
viewFieldGroupId?: Scalars['String']
__typename: 'NewFieldDefaultConfiguration'
}
export interface RichTextV2Body {
blocknote?: Scalars['String']
markdown?: Scalars['String']
@@ -887,7 +881,7 @@ export interface FieldRichTextConfiguration {
export interface FieldsConfiguration {
configurationType: WidgetConfigurationType
viewId?: Scalars['String']
newFieldDefaultConfiguration?: NewFieldDefaultConfiguration
newFieldDefaultVisibility?: Scalars['Boolean']
shouldAllowUserToSeeHiddenFields?: Scalars['Boolean']
__typename: 'FieldsConfiguration'
}
@@ -3488,13 +3482,6 @@ export interface RatioAggregateConfigGenqlSelection{
__scalar?: boolean | number
}
export interface NewFieldDefaultConfigurationGenqlSelection{
isVisible?: boolean | number
viewFieldGroupId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface RichTextV2BodyGenqlSelection{
blocknote?: boolean | number
markdown?: boolean | number
@@ -3752,7 +3739,7 @@ export interface FieldRichTextConfigurationGenqlSelection{
export interface FieldsConfigurationGenqlSelection{
configurationType?: boolean | number
viewId?: boolean | number
newFieldDefaultConfiguration?: NewFieldDefaultConfigurationGenqlSelection
newFieldDefaultVisibility?: boolean | number
shouldAllowUserToSeeHiddenFields?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
@@ -6452,14 +6439,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const NewFieldDefaultConfiguration_possibleTypes: string[] = ['NewFieldDefaultConfiguration']
export const isNewFieldDefaultConfiguration = (obj?: { __typename?: any } | null): obj is NewFieldDefaultConfiguration => {
if (!obj?.__typename) throw new Error('__typename is missing in "isNewFieldDefaultConfiguration"')
return NewFieldDefaultConfiguration_possibleTypes.includes(obj.__typename)
}
const RichTextV2Body_possibleTypes: string[] = ['RichTextV2Body']
export const isRichTextV2Body = (obj?: { __typename?: any } | null): obj is RichTextV2Body => {
if (!obj?.__typename) throw new Error('__typename is missing in "isRichTextV2Body"')
File diff suppressed because it is too large Load Diff
@@ -273,7 +273,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
}
case WidgetConfigurationType.FIELDS: {
const { viewId, newFieldDefaultConfiguration, ...rest } = configuration;
const { viewId, newFieldDefaultVisibility, ...rest } = configuration;
let viewUniversalIdentifier: string | null = null;
@@ -291,40 +291,10 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
}
}
if (!isDefined(newFieldDefaultConfiguration)) {
return {
...rest,
newFieldDefaultConfiguration,
viewId: viewUniversalIdentifier,
};
}
let viewFieldGroupUniversalIdentifier: string | null = null;
if (isDefined(newFieldDefaultConfiguration.viewFieldGroupId)) {
viewFieldGroupUniversalIdentifier =
viewFieldGroupUniversalIdentifierById[
newFieldDefaultConfiguration.viewFieldGroupId
] ?? null;
if (
!isDefined(viewFieldGroupUniversalIdentifier) &&
shouldThrowOnMissingIdentifier
) {
throw new FlatEntityMapsException(
`View field group universal identifier not found for id: ${newFieldDefaultConfiguration.viewFieldGroupId}`,
FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND,
);
}
}
return {
...rest,
newFieldDefaultVisibility,
viewId: viewUniversalIdentifier,
newFieldDefaultConfiguration: {
isVisible: newFieldDefaultConfiguration.isVisible,
viewFieldGroupId: viewFieldGroupUniversalIdentifier,
},
};
}
@@ -1,8 +1,8 @@
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
import { computeFlatViewFieldsFromFieldsWidgets } from 'src/engine/metadata-modules/flat-view-field/utils/compute-flat-view-fields-from-fields-widgets.util';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
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';
@@ -37,13 +37,24 @@ const buildFlatViewMaps = (
}) as unknown as FlatViewMaps;
const buildFlatViewFieldGroupMaps = (
entries: { id: string; universalIdentifier: string }[] = [],
entries: {
id: string;
universalIdentifier: string;
viewId?: string;
position?: number;
}[] = [],
): FlatViewFieldGroupMaps =>
({
byUniversalIdentifier: Object.fromEntries(
entries.map((entry) => [
entry.universalIdentifier,
{ universalIdentifier: entry.universalIdentifier, id: entry.id },
{
universalIdentifier: entry.universalIdentifier,
id: entry.id,
viewId: entry.viewId ?? VIEW_ID,
position: entry.position ?? 0,
deletedAt: null,
},
]),
),
universalIdentifierById: Object.fromEntries(
@@ -74,14 +85,12 @@ const buildFieldsWidget = ({
objectMetadataUniversalIdentifier = OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
viewId = VIEW_ID,
isVisible = true,
viewFieldGroupId = null as string | null,
deletedAt = null as string | null,
}: {
widgetUniversalIdentifier?: string;
objectMetadataUniversalIdentifier?: string;
viewId?: string | null;
isVisible?: boolean;
viewFieldGroupId?: string | null;
deletedAt?: string | null;
} = {}) => ({
universalIdentifier: widgetUniversalIdentifier,
@@ -91,10 +100,7 @@ const buildFieldsWidget = ({
configuration: {
configurationType: WidgetConfigurationType.FIELDS,
viewId,
newFieldDefaultConfiguration: {
isVisible,
viewFieldGroupId,
},
newFieldDefaultVisibility: isVisible,
},
universalConfiguration: null,
overrides: null,
@@ -478,7 +484,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
});
describe('view field group handling', () => {
it('should resolve viewFieldGroupUniversalIdentifier when viewFieldGroupId is set', () => {
it('should resolve to the last view field group when groups exist', () => {
const result = computeFlatViewFieldsFromFieldsWidgets({
fieldsToCreate: [
{
@@ -488,7 +494,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
},
],
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
buildFieldsWidget({ viewFieldGroupId: VIEW_FIELD_GROUP_ID }),
buildFieldsWidget(),
]),
flatViewFieldMaps: buildFlatViewFieldMaps(),
flatViewMaps: buildFlatViewMaps([
@@ -509,7 +515,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
);
});
it('should set viewFieldGroupUniversalIdentifier to null when viewFieldGroupId is null', () => {
it('should set viewFieldGroupUniversalIdentifier to null when no groups exist', () => {
const result = computeFlatViewFieldsFromFieldsWidgets({
fieldsToCreate: [
{
@@ -519,7 +525,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
},
],
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
buildFieldsWidget({ viewFieldGroupId: null }),
buildFieldsWidget(),
]),
flatViewFieldMaps: buildFlatViewFieldMaps(),
flatViewMaps: buildFlatViewMaps([
@@ -532,7 +538,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
expect(result[0].viewFieldGroupUniversalIdentifier).toBeNull();
});
it('should compute position only from view fields in the same group', () => {
it('should compute position only from view fields in the last group', () => {
const result = computeFlatViewFieldsFromFieldsWidgets({
fieldsToCreate: [
{
@@ -542,7 +548,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
},
],
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
buildFieldsWidget({ viewFieldGroupId: VIEW_FIELD_GROUP_ID }),
buildFieldsWidget(),
]),
flatViewFieldMaps: buildFlatViewFieldMaps([
{
@@ -582,7 +588,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
expect(result[0].position).toBe(3);
});
it('should compute position only from ungrouped view fields when viewFieldGroupId is null', () => {
it('should compute position only from ungrouped view fields when no groups exist', () => {
const result = computeFlatViewFieldsFromFieldsWidgets({
fieldsToCreate: [
{
@@ -592,7 +598,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
},
],
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
buildFieldsWidget({ viewFieldGroupId: null }),
buildFieldsWidget(),
]),
flatViewFieldMaps: buildFlatViewFieldMaps([
{
@@ -3,10 +3,10 @@ import { v4 } from 'uuid';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
import { type FlatViewMaps } from 'src/engine/metadata-modules/flat-view/types/flat-view-maps.type';
import { type FieldsConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/fields-configuration.dto';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
@@ -45,9 +45,33 @@ const getMatchingFieldsWidgets = ({
objectMetadataUniversalIdentifier &&
isFieldsWidgetConfiguration(widget.configuration) &&
isDefined(widget.configuration.viewId) &&
isDefined(widget.configuration.newFieldDefaultConfiguration),
isDefined(widget.configuration.newFieldDefaultVisibility),
);
const findLastViewFieldGroupId = ({
viewId,
flatViewFieldGroupMaps,
}: {
viewId: string;
flatViewFieldGroupMaps: FlatViewFieldGroupMaps;
}): string | null => {
const groupsForView = Object.values(
flatViewFieldGroupMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((group) => !isDefined(group.deletedAt) && group.viewId === viewId);
if (groupsForView.length === 0) {
return null;
}
const lastGroup = groupsForView.reduce((maxGroup, group) =>
group.position > maxGroup.position ? group : maxGroup,
);
return lastGroup.id;
};
const computeNextPosition = ({
viewId,
viewFieldGroupId,
@@ -127,8 +151,7 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
const configuration = widget.configuration;
const viewId = configuration.viewId!;
const { isVisible, viewFieldGroupId } =
configuration.newFieldDefaultConfiguration!;
const isVisible = configuration.newFieldDefaultVisibility!;
const viewUniversalIdentifier =
flatViewMaps.universalIdentifierById[viewId] ?? null;
@@ -137,6 +160,11 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
continue;
}
const viewFieldGroupId = findLastViewFieldGroupId({
viewId,
flatViewFieldGroupMaps,
});
const viewFieldGroupUniversalIdentifier = isDefined(viewFieldGroupId)
? (flatViewFieldGroupMaps.universalIdentifierById[viewFieldGroupId] ??
null)
@@ -1,30 +1,16 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsIn,
IsNotEmpty,
IsOptional,
IsUUID,
ValidateNested,
} from 'class-validator';
import { type FieldsConfiguration } from 'twenty-shared/types';
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
@ObjectType('NewFieldDefaultConfiguration')
export class NewFieldDefaultConfigurationDTO {
@Field(() => Boolean)
@IsBoolean()
isVisible: boolean;
@Field(() => String, { nullable: true })
@IsOptional()
@IsUUID()
viewFieldGroupId: string | null;
}
@ObjectType('FieldsConfiguration')
export class FieldsConfigurationDTO implements FieldsConfiguration {
@Field(() => WidgetConfigurationType)
@@ -37,11 +23,10 @@ export class FieldsConfigurationDTO implements FieldsConfiguration {
@IsUUID()
viewId: string | null;
@Field(() => NewFieldDefaultConfigurationDTO, { nullable: true })
@Field(() => Boolean, { nullable: true })
@IsOptional()
@ValidateNested()
@Type(() => NewFieldDefaultConfigurationDTO)
newFieldDefaultConfiguration: NewFieldDefaultConfigurationDTO | null;
@IsBoolean()
newFieldDefaultVisibility: boolean | null;
@Field(() => Boolean, { nullable: true })
@IsOptional()
@@ -128,18 +128,12 @@ const buildFieldsWidgetConfiguration = ({
configuration: {
configurationType: WidgetConfigurationType.FIELDS,
viewId: null,
newFieldDefaultConfiguration: {
isVisible: true,
viewFieldGroupId: null,
},
newFieldDefaultVisibility: true,
},
universalConfiguration: {
configurationType: WidgetConfigurationType.FIELDS,
viewId: null,
newFieldDefaultConfiguration: {
isVisible: true,
viewFieldGroupId: null,
},
newFieldDefaultVisibility: true,
},
};
}
@@ -167,32 +161,16 @@ const buildFieldsWidgetConfiguration = ({
const viewUniversalIdentifier = viewDefinition?.universalIdentifier ?? null;
const otherViewFieldGroupId =
views[recordPageFieldsViewName]?.viewFieldGroups?.other?.id ?? null;
const otherViewFieldGroupUniversalIdentifier =
viewDefinition?.viewFieldGroups?.other?.universalIdentifier ?? null;
const newFieldDefaultConfiguration = {
isVisible: true,
viewFieldGroupId: otherViewFieldGroupId,
};
const universalNewFieldDefaultConfiguration = {
isVisible: true,
viewFieldGroupId: otherViewFieldGroupUniversalIdentifier,
};
return {
configuration: {
configurationType: WidgetConfigurationType.FIELDS,
viewId,
newFieldDefaultConfiguration,
newFieldDefaultVisibility: true,
},
universalConfiguration: {
configurationType: WidgetConfigurationType.FIELDS,
viewId: viewUniversalIdentifier,
newFieldDefaultConfiguration: universalNewFieldDefaultConfiguration,
newFieldDefaultVisibility: true,
},
};
};
@@ -257,7 +257,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
case WidgetConfigurationType.FIELDS: {
const {
viewId: viewUniversalIdentifier,
newFieldDefaultConfiguration: universalNewFieldDefaultConfiguration,
newFieldDefaultVisibility,
...rest
} = universalConfiguration;
@@ -279,37 +279,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
viewId = flatView.id;
}
let newFieldDefaultConfiguration:
| { isVisible: boolean; viewFieldGroupId: string | null }
| null
| undefined = universalNewFieldDefaultConfiguration;
if (
isDefined(universalNewFieldDefaultConfiguration) &&
isDefined(universalNewFieldDefaultConfiguration.viewFieldGroupId)
) {
const viewFieldGroupUniversalIdentifier =
universalNewFieldDefaultConfiguration.viewFieldGroupId;
const flatViewFieldGroup = findFlatEntityByUniversalIdentifier({
flatEntityMaps: flatViewFieldGroupMaps,
universalIdentifier: viewFieldGroupUniversalIdentifier,
});
if (!isDefined(flatViewFieldGroup)) {
throw new FlatEntityMapsException(
`View field group not found for universal identifier: ${viewFieldGroupUniversalIdentifier}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
newFieldDefaultConfiguration = {
isVisible: universalNewFieldDefaultConfiguration.isVisible,
viewFieldGroupId: flatViewFieldGroup.id,
};
}
return { ...rest, viewId, newFieldDefaultConfiguration };
return { ...rest, viewId, newFieldDefaultVisibility };
}
case WidgetConfigurationType.FRONT_COMPONENT: {
@@ -95,15 +95,10 @@ export type FieldConfiguration = {
configurationType: 'FIELD';
};
type NewFieldDefaultConfiguration = {
isVisible: boolean;
viewFieldGroupId: string | null;
};
export type FieldsConfiguration = {
configurationType: 'FIELDS';
viewId?: string | null;
newFieldDefaultConfiguration?: NewFieldDefaultConfiguration | null;
newFieldDefaultVisibility?: boolean | null;
shouldAllowUserToSeeHiddenFields?: boolean;
};