breaking (soft) - Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (2/3) (#16277)

Should be merged once https://github.com/twentyhq/twenty/pull/16206 has
been released + command run to prod

In this PR
- Remove usage of viewGroup.fieldMetadataId, both in BE and FE states. 
- But we still need to properly populate it until we fully remove
viewGroup.fieldMetadataId from db and ORM entity (upcoming 3rd PR out of
3). fieldMetadataId was removed from CoreViewGroup type and
CreateViewGroupInput and is determined BE-side based on the associated
view's mainGroupByFieldMetadataId. **I expect this means a downtime on
viewGroup creation, until both FE and BE are deployed and cache is
flushed.** This seems acceptable to me as it only regards viewGroup
creation.
    - this information is replaced by view.mainGroupByFieldMetadataID
- Handle view group creation, update and deletion in the BE as a
side-effect of a view creation, update or deletion. Optimistic effects
are still used
- Add validation at view creation or update regarding
mainGroupByFieldMetadata

Left to do in 3rd PR
- Remove viewGroup.fieldMetadataId from db and ORM entity
- Restore feature allowing to update an existing grouped view's group by
field (already OK on BE side but need to rebuild FE optimistic)
This commit is contained in:
Marie
2025-12-05 15:00:03 +01:00
committed by GitHub
parent 17d88a074b
commit 7ce22d5c7e
87 changed files with 1338 additions and 1067 deletions
@@ -21,7 +21,6 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
return {
calendarViewIds: [],
viewFilterIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
viewFieldIds: [],
createdAt,
@@ -35,7 +35,6 @@ export const getRelationTargetFlatFieldMetadataMock = ({
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
viewFilterIds: [],
viewGroupIds: [],
viewFieldIds: [],
kanbanAggregateOperationViewIds: [],
createdAt,
@@ -137,7 +137,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
{
@@ -176,7 +175,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
{
@@ -213,7 +211,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
{
@@ -252,7 +249,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
],
@@ -41,43 +41,36 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
async computeForCache(
workspaceId: string,
): Promise<FlatEntityMaps<FlatFieldMetadata>> {
const [fieldMetadatas, viewFields, viewFilters, viewGroups, views] =
await Promise.all([
this.fieldMetadataRepository.find({
where: { workspaceId },
withDeleted: true,
}),
this.viewFieldRepository.find({
where: { workspaceId },
select: ['id', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewFilterRepository.find({
where: { workspaceId },
select: ['id', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewGroupRepository.find({
where: { workspaceId },
select: ['id', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewRepository.find({
where: { workspaceId },
select: [
'id',
'kanbanAggregateOperationFieldMetadataId',
'calendarFieldMetadataId',
'mainGroupByFieldMetadataId',
],
withDeleted: true,
}),
]);
const [fieldMetadatas, viewFields, viewFilters, views] = await Promise.all([
this.fieldMetadataRepository.find({
where: { workspaceId },
withDeleted: true,
}),
this.viewFieldRepository.find({
where: { workspaceId },
select: ['id', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewFilterRepository.find({
where: { workspaceId },
select: ['id', 'fieldMetadataId'],
withDeleted: true,
}),
this.viewRepository.find({
where: { workspaceId },
select: [
'id',
'kanbanAggregateOperationFieldMetadataId',
'calendarFieldMetadataId',
'mainGroupByFieldMetadataId',
],
withDeleted: true,
}),
]);
const [
viewFieldsByFieldId,
viewFiltersByFieldId,
viewGroupsByFieldId,
calendarViewsByFieldId,
kanbanViewsByFieldId,
mainGroupByFieldMetadataViewsByFieldId,
@@ -91,10 +84,6 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
entities: viewFilters,
foreignKey: 'fieldMetadataId',
},
{
entities: viewGroups,
foreignKey: 'fieldMetadataId',
},
{
entities: views,
foreignKey: 'calendarFieldMetadataId',
@@ -117,7 +106,6 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
...fieldMetadataEntity,
viewFields: viewFieldsByFieldId.get(fieldMetadataEntity.id) || [],
viewFilters: viewFiltersByFieldId.get(fieldMetadataEntity.id) || [],
viewGroups: viewGroupsByFieldId.get(fieldMetadataEntity.id) || [],
kanbanAggregateOperationViews:
kanbanViewsByFieldId.get(fieldMetadataEntity.id) || [],
calendarViews: calendarViewsByFieldId.get(fieldMetadataEntity.id) || [],
@@ -14,19 +14,20 @@ export const FIELD_METADATA_RELATION_PROPERTIES = [
'kanbanAggregateOperationViews',
'calendarViews',
'mainGroupByFieldMetadataViews',
'viewGroups',
] as const satisfies (keyof FieldMetadataEntity)[];
export type FieldMetadataEntityRelationProperties =
(typeof FIELD_METADATA_RELATION_PROPERTIES)[number];
export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
Omit<FieldMetadataEntity<T>, FieldMetadataEntityRelationProperties> & {
Omit<
FieldMetadataEntity<T>,
FieldMetadataEntityRelationProperties | 'viewGroups'
> & {
universalIdentifier: string;
viewFieldIds: string[];
viewFilterIds: string[];
kanbanAggregateOperationViewIds: string[];
calendarViewIds: string[];
mainGroupByFieldMetadataViewIds: string[];
viewGroupIds: string[];
};
@@ -26,7 +26,6 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
mainGroupByFieldMetadataViewIds:
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(({ id }) => id) ??
[],
viewGroupIds: fieldMetadataEntity.viewGroups.map(({ id }) => id),
viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id),
viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id),
universalIdentifier:
@@ -60,7 +60,6 @@ export const getDefaultFlatFieldMetadata = ({
morphId: null,
applicationId: workspaceCustomApplicationId,
viewFilterIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
} as const satisfies FlatFieldMetadata;
};
@@ -17,7 +17,10 @@ type HandleEnumFlatFieldMetadataOptionsUpdateSideEffectsArgs = FromTo<
FlatFieldMetadata<EnumFieldMetadataType>,
'flatFieldMetadata'
> &
Pick<AllFlatEntityMaps, 'flatViewFilterMaps' | 'flatViewGroupMaps'>;
Pick<
AllFlatEntityMaps,
'flatViewFilterMaps' | 'flatViewGroupMaps' | 'flatViewMaps'
>;
type EnumFieldMetadataSideEffectResult = FlatViewGroupsToDeleteUpdateAndCreate &
FlatViewFiltersToDeleteAndUpdate;
@@ -36,6 +39,7 @@ export const handleEnumFlatFieldMetadataUpdateSideEffects = ({
toFlatFieldMetadata,
flatViewFilterMaps,
flatViewGroupMaps,
flatViewMaps,
}: HandleEnumFlatFieldMetadataOptionsUpdateSideEffectsArgs): EnumFieldMetadataSideEffectResult => {
const sideEffectResult = structuredClone(
EMPTY_ENUM_FIELD_METADATA_SIDE_EFFECT_RESULT,
@@ -69,6 +73,7 @@ export const handleEnumFlatFieldMetadataUpdateSideEffects = ({
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
} = recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate({
flatViewMaps,
flatViewGroupMaps,
fromFlatFieldMetadata,
update: optionsPropertyUpdate,
@@ -82,6 +87,7 @@ export const handleEnumFlatFieldMetadataUpdateSideEffects = ({
if (fromFlatFieldMetadata.isNullable !== toFlatFieldMetadata.isNullable) {
const { flatViewGroupsToCreate, flatViewGroupsToDelete } =
recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate({
flatViewMaps,
flatViewGroupMaps,
fromFlatFieldMetadata,
toFlatFieldMetadata,
@@ -46,8 +46,16 @@ export const handleFieldMetadataDeactivationSideEffects = ({
flatEntityMaps: flatViewFieldMaps,
});
const flatViewsAffected: FlatView[] =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.mainGroupByFieldMetadataViewIds,
flatEntityMaps: flatViewMaps,
});
const flatViewGroups = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewGroupIds,
flatEntityIds: flatViewsAffected.flatMap(
(flatView) => flatView.viewGroupIds,
),
flatEntityMaps: flatViewGroupMaps,
});
@@ -60,6 +68,7 @@ export const handleFieldMetadataDeactivationSideEffects = ({
...new Set([
...Object.keys(flatViewGroupRecordByViewId),
...fromFlatFieldMetadata.calendarViewIds,
...fromFlatFieldMetadata.mainGroupByFieldMetadataViewIds,
]),
];
@@ -100,6 +100,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
} = handleEnumFlatFieldMetadataUpdateSideEffects({
flatViewMaps,
flatViewFilterMaps,
flatViewGroupMaps,
fromFlatFieldMetadata,
@@ -12,7 +12,7 @@ type RecomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdateArgs = FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
> &
Pick<AllFlatEntityMaps, 'flatViewGroupMaps'>;
Pick<AllFlatEntityMaps, 'flatViewMaps' | 'flatViewGroupMaps'>;
type EnumFieldMetadataIsNullableUpdateSideEffect = {
flatViewGroupsToDelete: FlatViewGroup[];
@@ -25,6 +25,7 @@ const EMPTY_ENUM_FIELD_METADATA_IS_NULLABLE_UPDATE_SIDE_EFFECT_RESULT: EnumField
};
export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
flatViewMaps,
flatViewGroupMaps: allFlatViewGroups,
fromFlatFieldMetadata,
toFlatFieldMetadata,
@@ -35,8 +36,15 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
const sideEffectResult = structuredClone(
EMPTY_ENUM_FIELD_METADATA_IS_NULLABLE_UPDATE_SIDE_EFFECT_RESULT,
);
const flatViewsAffected = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.mainGroupByFieldMetadataViewIds,
flatEntityMaps: flatViewMaps,
});
const flatViewGroups = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewGroupIds,
flatEntityIds: flatViewsAffected.flatMap(
(flatView) => flatView.viewGroupIds,
),
flatEntityMaps: allFlatViewGroups,
});
const { flatViewGroupRecordByViewId, highestViewGroupPositionByViewId } =
@@ -60,7 +68,6 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
const createdAt = new Date();
sideEffectResult.flatViewGroupsToCreate.push({
fieldMetadataId: toFlatFieldMetadata.id,
id: viewGroupId,
universalIdentifier: viewGroupId,
fieldValue: '',
@@ -72,6 +79,7 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
deletedAt: null,
viewId,
applicationId: toFlatFieldMetadata.applicationId,
fieldMetadataId: fromFlatFieldMetadata.id,
});
} else if (isDefined(emptyValueFlatViewGroup)) {
sideEffectResult.flatViewGroupsToDelete.push(emptyValueFlatViewGroup);
@@ -17,7 +17,7 @@ import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migr
type RecomputeViewGroupsOnFlatFieldMetadataOptionsUpdateArgs = {
fromFlatFieldMetadata: FlatFieldMetadata<EnumFieldMetadataType>;
update: PropertyUpdate<FlatFieldMetadata<EnumFieldMetadataType>, 'options'>;
} & Pick<AllFlatEntityMaps, 'flatViewGroupMaps'>;
} & Pick<AllFlatEntityMaps, 'flatViewMaps' | 'flatViewGroupMaps'>;
export type FlatViewGroupsToDeleteUpdateAndCreate = {
flatViewGroupsToDelete: FlatViewGroup[];
@@ -25,6 +25,7 @@ export type FlatViewGroupsToDeleteUpdateAndCreate = {
flatViewGroupsToCreate: FlatViewGroup[];
};
export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({
flatViewMaps,
flatViewGroupMaps,
fromFlatFieldMetadata,
update,
@@ -39,8 +40,15 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({
toOptions: update.to,
});
const flatViewsAffected = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.mainGroupByFieldMetadataViewIds,
flatEntityMaps: flatViewMaps,
});
const flatViewGroups = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewGroupIds,
flatEntityIds: flatViewsAffected.flatMap(
(flatView) => flatView.viewGroupIds,
),
flatEntityMaps: flatViewGroupMaps,
});
@@ -63,8 +71,7 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({
const flatViewGroupsToUpdate = updatedFieldMetadataOptions.flatMap(
({ from: fromOption, to: toOption }) =>
flatViewGroups.flatMap((flatViewGroup) =>
flatViewGroup.fieldValue === fromOption.value &&
flatViewGroup.fieldMetadataId === fromFlatFieldMetadata.id
flatViewGroup.fieldValue === fromOption.value
? { ...flatViewGroup, fieldValue: toOption.value }
: [],
),