Sync views and navigation items (#18003)

Both objects are necessary to fully enjoy objects within applications

<img width="770" height="311" alt="Capture d’écran 2026-02-17 à 15 19
43"
src="https://github.com/user-attachments/assets/48c51fa4-63f4-45b2-a40a-df73f3aa79be"
/>
This commit is contained in:
Thomas Trompette
2026-02-17 16:32:41 +01:00
committed by GitHub
parent 63a3f93a78
commit b4e924b671
134 changed files with 1029 additions and 180 deletions
@@ -6,6 +6,13 @@ export const APPLICATION_MANIFEST_METADATA_NAMES = [
'logicFunction',
'frontComponent',
'role',
'view',
'viewField',
'viewFieldGroup',
'viewFilter',
'viewFilterGroup',
'viewGroup',
'navigationMenuItem',
] as const satisfies AllMetadataName[];
export type ApplicationManifestMetadataName =
@@ -0,0 +1,53 @@
import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/utils/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util';
describe('fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem', () => {
const now = '2026-01-01T00:00:00.000Z';
const applicationUniversalIdentifier = 'app-uuid-1';
it('should convert a minimal navigation menu item manifest', () => {
const result =
fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem({
navigationMenuItemManifest: {
universalIdentifier: 'nav-uuid-1',
position: 0,
},
applicationUniversalIdentifier,
now,
});
expect(result.universalIdentifier).toBe('nav-uuid-1');
expect(result.applicationUniversalIdentifier).toBe(
applicationUniversalIdentifier,
);
expect(result.position).toBe(0);
expect(result.name).toBeNull();
expect(result.viewUniversalIdentifier).toBeNull();
expect(result.link).toBeNull();
expect(result.folderUniversalIdentifier).toBeNull();
expect(result.targetObjectMetadataUniversalIdentifier).toBeNull();
expect(result.targetRecordId).toBeNull();
expect(result.userWorkspaceId).toBeNull();
});
it('should convert a fully specified navigation menu item manifest', () => {
const result =
fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem({
navigationMenuItemManifest: {
universalIdentifier: 'nav-uuid-2',
name: 'Recipes Board',
position: 1,
viewUniversalIdentifier: 'view-uuid-1',
folderUniversalIdentifier: 'nav-folder-uuid-1',
targetObjectUniversalIdentifier: 'obj-uuid-1',
},
applicationUniversalIdentifier,
now,
});
expect(result.name).toBe('Recipes Board');
expect(result.position).toBe(1);
expect(result.viewUniversalIdentifier).toBe('view-uuid-1');
expect(result.folderUniversalIdentifier).toBe('nav-folder-uuid-1');
expect(result.targetObjectMetadataUniversalIdentifier).toBe('obj-uuid-1');
});
});
@@ -0,0 +1,54 @@
import { AggregateOperations } from 'twenty-shared/types';
import { fromViewFieldManifestToUniversalFlatViewField } from 'src/engine/core-modules/application/utils/from-view-field-manifest-to-universal-flat-view-field.util';
describe('fromViewFieldManifestToUniversalFlatViewField', () => {
const now = '2026-01-01T00:00:00.000Z';
const applicationUniversalIdentifier = 'app-uuid-1';
const viewUniversalIdentifier = 'view-uuid-1';
it('should convert a view field manifest with defaults', () => {
const result = fromViewFieldManifestToUniversalFlatViewField({
viewFieldManifest: {
universalIdentifier: 'vf-uuid-1',
fieldMetadataUniversalIdentifier: 'field-uuid-1',
position: 0,
},
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
});
expect(result.universalIdentifier).toBe('vf-uuid-1');
expect(result.fieldMetadataUniversalIdentifier).toBe('field-uuid-1');
expect(result.viewUniversalIdentifier).toBe(viewUniversalIdentifier);
expect(result.isVisible).toBe(true);
expect(result.size).toBe(0);
expect(result.position).toBe(0);
expect(result.aggregateOperation).toBeNull();
expect(result.viewFieldGroupUniversalIdentifier).toBeNull();
});
it('should respect explicit values', () => {
const result = fromViewFieldManifestToUniversalFlatViewField({
viewFieldManifest: {
universalIdentifier: 'vf-uuid-2',
fieldMetadataUniversalIdentifier: 'field-uuid-2',
position: 5,
isVisible: false,
size: 200,
aggregateOperation: AggregateOperations.COUNT,
viewFieldGroupUniversalIdentifier: 'vfg-uuid-1',
},
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
});
expect(result.isVisible).toBe(false);
expect(result.size).toBe(200);
expect(result.position).toBe(5);
expect(result.aggregateOperation).toBe(AggregateOperations.COUNT);
expect(result.viewFieldGroupUniversalIdentifier).toBe('vfg-uuid-1');
});
});
@@ -0,0 +1,53 @@
import { ViewFilterOperand } from 'twenty-shared/types';
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/utils/from-view-filter-manifest-to-universal-flat-view-filter.util';
describe('fromViewFilterManifestToUniversalFlatViewFilter', () => {
const now = '2026-01-01T00:00:00.000Z';
const applicationUniversalIdentifier = 'app-uuid-1';
const viewUniversalIdentifier = 'view-uuid-1';
it('should convert a view filter manifest with defaults', () => {
const result = fromViewFilterManifestToUniversalFlatViewFilter({
viewFilterManifest: {
universalIdentifier: 'vfilter-uuid-1',
fieldMetadataUniversalIdentifier: 'field-uuid-1',
operand: ViewFilterOperand.CONTAINS,
value: 'test',
},
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
});
expect(result.universalIdentifier).toBe('vfilter-uuid-1');
expect(result.fieldMetadataUniversalIdentifier).toBe('field-uuid-1');
expect(result.viewUniversalIdentifier).toBe(viewUniversalIdentifier);
expect(result.operand).toBe(ViewFilterOperand.CONTAINS);
expect(result.value).toBe('test');
expect(result.subFieldName).toBeNull();
expect(result.viewFilterGroupUniversalIdentifier).toBeNull();
expect(result.positionInViewFilterGroup).toBeNull();
});
it('should respect explicit optional values', () => {
const result = fromViewFilterManifestToUniversalFlatViewFilter({
viewFilterManifest: {
universalIdentifier: 'vfilter-uuid-2',
fieldMetadataUniversalIdentifier: 'field-uuid-2',
operand: ViewFilterOperand.IS,
value: ['a', 'b'],
subFieldName: 'city',
viewFilterGroupUniversalIdentifier: 'vfg-uuid-1',
positionInViewFilterGroup: 2,
},
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
});
expect(result.subFieldName).toBe('city');
expect(result.viewFilterGroupUniversalIdentifier).toBe('vfg-uuid-1');
expect(result.positionInViewFilterGroup).toBe(2);
});
});
@@ -0,0 +1,66 @@
import {
ViewOpenRecordIn,
ViewType,
ViewVisibility,
} from 'twenty-shared/types';
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/utils/from-view-manifest-to-universal-flat-view.util';
describe('fromViewManifestToUniversalFlatView', () => {
const now = '2026-01-01T00:00:00.000Z';
const applicationUniversalIdentifier = 'app-uuid-1';
it('should convert a minimal view manifest to universal flat view', () => {
const result = fromViewManifestToUniversalFlatView({
viewManifest: {
universalIdentifier: 'view-uuid-1',
name: 'All Records',
objectUniversalIdentifier: 'object-uuid-1',
},
applicationUniversalIdentifier,
now,
});
expect(result.universalIdentifier).toBe('view-uuid-1');
expect(result.applicationUniversalIdentifier).toBe(
applicationUniversalIdentifier,
);
expect(result.name).toBe('All Records');
expect(result.objectMetadataUniversalIdentifier).toBe('object-uuid-1');
expect(result.type).toBe(ViewType.TABLE);
expect(result.icon).toBe('IconList');
expect(result.position).toBe(0);
expect(result.isCompact).toBe(false);
expect(result.isCustom).toBe(true);
expect(result.visibility).toBe(ViewVisibility.WORKSPACE);
expect(result.openRecordIn).toBe(ViewOpenRecordIn.SIDE_PANEL);
expect(result.key).toBeNull();
expect(result.createdAt).toBe(now);
expect(result.updatedAt).toBe(now);
});
it('should respect explicit values from the manifest', () => {
const result = fromViewManifestToUniversalFlatView({
viewManifest: {
universalIdentifier: 'view-uuid-2',
name: 'Kanban Board',
objectUniversalIdentifier: 'object-uuid-1',
type: ViewType.KANBAN,
icon: 'IconLayoutKanban',
position: 3,
isCompact: true,
visibility: ViewVisibility.UNLISTED,
openRecordIn: ViewOpenRecordIn.RECORD_PAGE,
},
applicationUniversalIdentifier,
now,
});
expect(result.type).toBe(ViewType.KANBAN);
expect(result.icon).toBe('IconLayoutKanban');
expect(result.position).toBe(3);
expect(result.isCompact).toBe(true);
expect(result.visibility).toBe(ViewVisibility.UNLISTED);
expect(result.openRecordIn).toBe(ViewOpenRecordIn.RECORD_PAGE);
});
});
@@ -4,8 +4,15 @@ import { type ApplicationManifestMetadataName } from 'src/engine/core-modules/ap
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/utils/from-field-manifest-to-universal-flat-field-metadata.util';
import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/utils/from-front-component-manifest-to-universal-flat-front-component.util';
import { fromLogicFunctionManifestToUniversalFlatLogicFunction } from 'src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util';
import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/utils/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util';
import { fromObjectManifestToUniversalFlatObjectMetadata } from 'src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util';
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/utils/from-role-manifest-to-universal-flat-role.util';
import { fromViewFieldGroupManifestToUniversalFlatViewFieldGroup } from 'src/engine/core-modules/application/utils/from-view-field-group-manifest-to-universal-flat-view-field-group.util';
import { fromViewFieldManifestToUniversalFlatViewField } from 'src/engine/core-modules/application/utils/from-view-field-manifest-to-universal-flat-view-field.util';
import { fromViewFilterGroupManifestToUniversalFlatViewFilterGroup } from 'src/engine/core-modules/application/utils/from-view-filter-group-manifest-to-universal-flat-view-filter-group.util';
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/utils/from-view-filter-manifest-to-universal-flat-view-filter.util';
import { fromViewGroupManifestToUniversalFlatViewGroup } from 'src/engine/core-modules/application/utils/from-view-group-manifest-to-universal-flat-view-group.util';
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/utils/from-view-manifest-to-universal-flat-view.util';
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
@@ -123,5 +130,111 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
);
}
for (const viewManifest of manifest.views ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'view',
universalFlatEntity: fromViewManifestToUniversalFlatView({
viewManifest,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
for (const viewFieldGroupManifest of viewManifest.fieldGroups ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'viewFieldGroup',
universalFlatEntity:
fromViewFieldGroupManifestToUniversalFlatViewFieldGroup({
viewFieldGroupManifest,
viewUniversalIdentifier: viewManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
for (const viewFieldManifest of viewManifest.fields ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'viewField',
universalFlatEntity: fromViewFieldManifestToUniversalFlatViewField({
viewFieldManifest,
viewUniversalIdentifier: viewManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
for (const viewFilterGroupManifest of viewManifest.filterGroups ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'viewFilterGroup',
universalFlatEntity:
fromViewFilterGroupManifestToUniversalFlatViewFilterGroup({
viewFilterGroupManifest,
viewUniversalIdentifier: viewManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
for (const viewFilterManifest of viewManifest.filters ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'viewFilter',
universalFlatEntity: fromViewFilterManifestToUniversalFlatViewFilter({
viewFilterManifest,
viewUniversalIdentifier: viewManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
for (const viewGroupManifest of viewManifest.groups ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'viewGroup',
universalFlatEntity: fromViewGroupManifestToUniversalFlatViewGroup({
viewGroupManifest,
viewUniversalIdentifier: viewManifest.universalIdentifier,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
}
for (const navigationMenuItemManifest of manifest.navigationMenuItems ?? []) {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName: 'navigationMenuItem',
universalFlatEntity:
fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem({
navigationMenuItemManifest,
applicationUniversalIdentifier,
now,
}),
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
},
);
}
return allUniversalFlatEntityMaps;
};
@@ -0,0 +1,32 @@
import { type NavigationMenuItemManifest } from 'twenty-shared/application';
import { type UniversalFlatNavigationMenuItem } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-navigation-menu-item.type';
export const fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem =
({
navigationMenuItemManifest,
applicationUniversalIdentifier,
now,
}: {
navigationMenuItemManifest: NavigationMenuItemManifest;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatNavigationMenuItem => {
return {
universalIdentifier: navigationMenuItemManifest.universalIdentifier,
applicationUniversalIdentifier,
name: navigationMenuItemManifest.name ?? null,
position: navigationMenuItemManifest.position,
viewUniversalIdentifier:
navigationMenuItemManifest.viewUniversalIdentifier ?? null,
link: navigationMenuItemManifest.link ?? null,
folderUniversalIdentifier:
navigationMenuItemManifest.folderUniversalIdentifier ?? null,
targetObjectMetadataUniversalIdentifier:
navigationMenuItemManifest.targetObjectUniversalIdentifier ?? null,
targetRecordId: null,
userWorkspaceId: null,
createdAt: now,
updatedAt: now,
};
};
@@ -0,0 +1,28 @@
import { type ViewFieldGroupManifest } from 'twenty-shared/application';
import { type UniversalFlatViewFieldGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field-group.type';
export const fromViewFieldGroupManifestToUniversalFlatViewFieldGroup = ({
viewFieldGroupManifest,
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
viewFieldGroupManifest: ViewFieldGroupManifest;
viewUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatViewFieldGroup => {
return {
universalIdentifier: viewFieldGroupManifest.universalIdentifier,
applicationUniversalIdentifier,
viewUniversalIdentifier,
name: viewFieldGroupManifest.name ?? '',
position: viewFieldGroupManifest.position,
isVisible: viewFieldGroupManifest.isVisible ?? true,
viewFieldUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
deletedAt: null,
};
};
@@ -0,0 +1,32 @@
import { type ViewFieldManifest } from 'twenty-shared/application';
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
export const fromViewFieldManifestToUniversalFlatViewField = ({
viewFieldManifest,
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
viewFieldManifest: ViewFieldManifest;
viewUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatViewField => {
return {
universalIdentifier: viewFieldManifest.universalIdentifier,
applicationUniversalIdentifier,
fieldMetadataUniversalIdentifier:
viewFieldManifest.fieldMetadataUniversalIdentifier,
viewUniversalIdentifier,
viewFieldGroupUniversalIdentifier:
viewFieldManifest.viewFieldGroupUniversalIdentifier ?? null,
isVisible: viewFieldManifest.isVisible ?? true,
size: viewFieldManifest.size ?? 0,
position: viewFieldManifest.position,
aggregateOperation: viewFieldManifest.aggregateOperation ?? null,
createdAt: now,
updatedAt: now,
deletedAt: null,
};
};
@@ -0,0 +1,31 @@
import { type ViewFilterGroupManifest } from 'twenty-shared/application';
import { type UniversalFlatViewFilterGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-filter-group.type';
export const fromViewFilterGroupManifestToUniversalFlatViewFilterGroup = ({
viewFilterGroupManifest,
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
viewFilterGroupManifest: ViewFilterGroupManifest;
viewUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatViewFilterGroup => {
return {
universalIdentifier: viewFilterGroupManifest.universalIdentifier,
applicationUniversalIdentifier,
viewUniversalIdentifier,
parentViewFilterGroupUniversalIdentifier:
viewFilterGroupManifest.parentViewFilterGroupUniversalIdentifier ?? null,
logicalOperator: viewFilterGroupManifest.logicalOperator,
positionInViewFilterGroup:
viewFilterGroupManifest.positionInViewFilterGroup ?? null,
childViewFilterGroupUniversalIdentifiers: [],
viewFilterUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
deletedAt: null,
};
};
@@ -0,0 +1,33 @@
import { type ViewFilterManifest } from 'twenty-shared/application';
import { type UniversalFlatViewFilter } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-filter.type';
export const fromViewFilterManifestToUniversalFlatViewFilter = ({
viewFilterManifest,
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
viewFilterManifest: ViewFilterManifest;
viewUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatViewFilter => {
return {
universalIdentifier: viewFilterManifest.universalIdentifier,
applicationUniversalIdentifier,
fieldMetadataUniversalIdentifier:
viewFilterManifest.fieldMetadataUniversalIdentifier,
viewUniversalIdentifier,
viewFilterGroupUniversalIdentifier:
viewFilterManifest.viewFilterGroupUniversalIdentifier ?? null,
operand: viewFilterManifest.operand,
value: viewFilterManifest.value,
subFieldName: viewFilterManifest.subFieldName ?? null,
positionInViewFilterGroup:
viewFilterManifest.positionInViewFilterGroup ?? null,
createdAt: now,
updatedAt: now,
deletedAt: null,
};
};
@@ -0,0 +1,27 @@
import { type ViewGroupManifest } from 'twenty-shared/application';
import { type UniversalFlatViewGroup } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-group.type';
export const fromViewGroupManifestToUniversalFlatViewGroup = ({
viewGroupManifest,
viewUniversalIdentifier,
applicationUniversalIdentifier,
now,
}: {
viewGroupManifest: ViewGroupManifest;
viewUniversalIdentifier: string;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatViewGroup => {
return {
universalIdentifier: viewGroupManifest.universalIdentifier,
applicationUniversalIdentifier,
viewUniversalIdentifier,
fieldValue: viewGroupManifest.fieldValue,
isVisible: viewGroupManifest.isVisible ?? true,
position: viewGroupManifest.position,
createdAt: now,
updatedAt: now,
deletedAt: null,
};
};
@@ -0,0 +1,49 @@
import { type ViewManifest } from 'twenty-shared/application';
import {
ViewOpenRecordIn,
ViewType,
ViewVisibility,
} from 'twenty-shared/types';
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
export const fromViewManifestToUniversalFlatView = ({
viewManifest,
applicationUniversalIdentifier,
now,
}: {
viewManifest: ViewManifest;
applicationUniversalIdentifier: string;
now: string;
}): UniversalFlatView => {
return {
universalIdentifier: viewManifest.universalIdentifier,
applicationUniversalIdentifier,
name: viewManifest.name,
objectMetadataUniversalIdentifier: viewManifest.objectUniversalIdentifier,
type: viewManifest.type ?? ViewType.TABLE,
icon: viewManifest.icon ?? 'IconList',
position: viewManifest.position ?? 0,
isCompact: viewManifest.isCompact ?? false,
isCustom: true,
visibility: viewManifest.visibility ?? ViewVisibility.WORKSPACE,
openRecordIn: viewManifest.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL,
key: null,
kanbanAggregateOperation: null,
kanbanAggregateOperationFieldMetadataUniversalIdentifier: null,
calendarLayout: null,
calendarFieldMetadataUniversalIdentifier: null,
mainGroupByFieldMetadataUniversalIdentifier: null,
shouldHideEmptyGroups: false,
anyFieldFilterValue: null,
createdByUserWorkspaceId: null,
viewFieldUniversalIdentifiers: [],
viewFilterUniversalIdentifiers: [],
viewFilterGroupUniversalIdentifiers: [],
viewGroupUniversalIdentifiers: [],
viewFieldGroupUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
deletedAt: null,
};
};