[Requires "warm" cache flush (no immediate downtime before flush)] Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (1/3) (#16206)

In this PR (1/3)
- introduce view.mainGroupByFieldMetadataId as the new reference
determining which fieldMetadataId is used in a grouped view, in order to
deprecate viewGroup.fieldMetadataId which creates inconsistencies.
view.mainGroupByFieldMetadataId is now filled at every view creation,
though not in use yet.
- Introduce a command to backfill view.mainGroupByFieldMetadataId for
existing views + delete all viewGroup.fieldMetadataId with a
fieldMetadataId that is not view.mainGroupByFieldMetadataId. (It should
concern 37 active workspaces)
- Temporarily disable the option to change a grouped view's
fieldMetadataId as for now it creates inconsistencies. This feature can
be reintroduced when we have done the full migration.

In a next PR
- (2/3) use view.mainGroupByFieldMetadataId instead of
viewGroup.fieldMetadataId. In FE we may keep viewGroup.fieldMetadataId
as a state (TBD). View groups will now be created / deleted as a side
effect of view's mainGroupByFieldMetadataId update.
- (3/3) remove viewGroup.fieldMetadataId

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Marie
2025-12-02 16:31:07 +01:00
committed by GitHub
parent aa729a2a0a
commit 77409b6eb2
71 changed files with 484 additions and 99 deletions
@@ -25,6 +25,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
kanbanAggregateOperationViewIds: [],
viewFieldIds: [],
createdAt,
mainGroupByFieldMetadataViewIds: [],
updatedAt: createdAt,
defaultValue: null,
options: null,
@@ -33,6 +33,7 @@ export const getRelationTargetFlatFieldMetadataMock = ({
return {
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
viewFilterIds: [],
viewGroupIds: [],
viewFieldIds: [],
@@ -120,6 +120,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUnique": null,
"kanbanAggregateOperationViewIds": [],
"label": "newFieldLabel",
"mainGroupByFieldMetadataViewIds": [],
"morphId": Any<String>,
"name": "newFieldPets",
"objectMetadataId": Any<String>,
@@ -156,6 +157,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUnique": null,
"kanbanAggregateOperationViewIds": [],
"label": "Pet",
"mainGroupByFieldMetadataViewIds": [],
"morphId": null,
"name": "pet",
"objectMetadataId": Any<String>,
@@ -194,6 +196,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUnique": null,
"kanbanAggregateOperationViewIds": [],
"label": "newFieldLabel",
"mainGroupByFieldMetadataViewIds": [],
"morphId": Any<String>,
"name": "newFieldCompanies",
"objectMetadataId": Any<String>,
@@ -230,6 +233,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUnique": null,
"kanbanAggregateOperationViewIds": [],
"label": "Company",
"mainGroupByFieldMetadataViewIds": [],
"morphId": null,
"name": "company",
"objectMetadataId": Any<String>,
@@ -68,6 +68,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
'id',
'kanbanAggregateOperationFieldMetadataId',
'calendarFieldMetadataId',
'mainGroupByFieldMetadataId',
],
withDeleted: true,
}),
@@ -79,6 +80,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
viewGroupsByFieldId,
calendarViewsByFieldId,
kanbanViewsByFieldId,
mainGroupByFieldMetadataViewsByFieldId,
] = (
[
{
@@ -101,6 +103,10 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
entities: views,
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
},
{
entities: views,
foreignKey: 'mainGroupByFieldMetadataId',
},
] as const
).map(regroupEntitiesByRelatedEntityId);
@@ -115,6 +121,9 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
kanbanAggregateOperationViews:
kanbanViewsByFieldId.get(fieldMetadataEntity.id) || [],
calendarViews: calendarViewsByFieldId.get(fieldMetadataEntity.id) || [],
mainGroupByFieldMetadataViews:
mainGroupByFieldMetadataViewsByFieldId.get(fieldMetadataEntity.id) ||
[],
} as FieldMetadataEntity);
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
@@ -11,9 +11,10 @@ export const FIELD_METADATA_RELATION_PROPERTIES = [
'viewFields',
'application',
'viewFilters',
'viewGroups',
'kanbanAggregateOperationViews',
'calendarViews',
'mainGroupByFieldMetadataViews',
'viewGroups',
] as const satisfies (keyof FieldMetadataEntity)[];
export type FieldMetadataEntityRelationProperties =
@@ -24,7 +25,8 @@ export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
universalIdentifier: string;
viewFieldIds: string[];
viewFilterIds: string[];
viewGroupIds: string[];
kanbanAggregateOperationViewIds: string[];
calendarViewIds: string[];
mainGroupByFieldMetadataViewIds: string[];
viewGroupIds: string[];
};
@@ -23,6 +23,9 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
kanbanAggregateOperationViewIds:
fieldMetadataEntity.kanbanAggregateOperationViews.map(({ id }) => id),
calendarViewIds: fieldMetadataEntity.calendarViews.map(({ id }) => id),
mainGroupByFieldMetadataViewIds:
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(({ id }) => id) ??
[],
viewGroupIds: fieldMetadataEntity.viewGroups.map(({ id }) => id),
viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id),
viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id),
@@ -26,6 +26,7 @@ export const getDefaultFlatFieldMetadata = ({
return {
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
viewFieldIds: [],
description: createFieldInput.description ?? null,
id: fieldMetadataId,
@@ -1,5 +1,5 @@
import { isDefined } from 'class-validator';
import { type EnumFieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import {
@@ -63,7 +63,8 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({
const flatViewGroupsToUpdate = updatedFieldMetadataOptions.flatMap(
({ from: fromOption, to: toOption }) =>
flatViewGroups.flatMap((flatViewGroup) =>
flatViewGroup.fieldValue === fromOption.value
flatViewGroup.fieldValue === fromOption.value &&
flatViewGroup.fieldMetadataId === fromFlatFieldMetadata.id
? { ...flatViewGroup, fieldValue: toOption.value }
: [],
),