Files
twenty/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts
T
Paul Rastoin 3dc5b162c7 Spread in parent and requires FlatEntity.__universal (#17753)
# Introduction
Requiring the spreaded `__universal` record that aggregates all the
universal identifier ( relations fk and aggregators ) of an entity to
its root

It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be
finalized because if we don't we would have to migrated all related
entities at once in order for them to always have the universal
properties

## `resolveEntityRelationUniversalIdentifiers`
Introduced `resolveEntityRelationUniversalIdentifiers` a centralized
utility that resolves foreign key IDs to universal identifiers using
ALL_METADATA_RELATIONS metadata. It provides strict typing for both
input (foreign keys) and output (universal identifiers), with
nullability dynamically inferred from entity relation types.
Strictly and dynamically typed for both output and input

To do so added a new type and const/runtime grain to
ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from
the entity relation property types. And fixed incorrectly typed typeorm
entities

### Usage
```ts
  const {
    availabilityObjectMetadataUniversalIdentifier,
    frontComponentUniversalIdentifier,
  } = resolveEntityRelationUniversalIdentifiers({
    metadataName: 'commandMenuItem',
    foreignKeyValues: {
      availabilityObjectMetadataId:
        createCommandMenuItemInput.availabilityObjectMetadataId,
      frontComponentId: createCommandMenuItemInput.frontComponentId,
    },
    flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps },
  });
```
2026-02-06 17:02:02 +00:00

106 lines
4.1 KiB
TypeScript

import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
import { reduceFlatViewGroupsByViewUniversalIdentifier } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-universal-identifier.util';
type RecomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdateArgs = FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
> &
Pick<AllFlatEntityMaps, 'flatViewMaps' | 'flatViewGroupMaps'>;
type EnumFieldMetadataIsNullableUpdateSideEffect = {
flatViewGroupsToDelete: FlatViewGroup[];
flatViewGroupsToCreate: FlatViewGroup[];
};
const EMPTY_ENUM_FIELD_METADATA_IS_NULLABLE_UPDATE_SIDE_EFFECT_RESULT: EnumFieldMetadataIsNullableUpdateSideEffect =
{
flatViewGroupsToCreate: [],
flatViewGroupsToDelete: [],
};
export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
flatViewMaps,
flatViewGroupMaps: allFlatViewGroups,
fromFlatFieldMetadata,
toFlatFieldMetadata,
}: RecomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdateArgs): {
flatViewGroupsToDelete: FlatViewGroup[];
flatViewGroupsToCreate: FlatViewGroup[];
} => {
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: flatViewsAffected.flatMap(
(flatView) => flatView.viewGroupIds,
),
flatEntityMaps: allFlatViewGroups,
});
const {
flatViewGroupRecordByViewUniversalIdentifier,
highestViewGroupPositionByViewUniversalIdentifier,
} = reduceFlatViewGroupsByViewUniversalIdentifier({
flatViewGroups,
});
for (const viewUniversalIdentifier in flatViewGroupRecordByViewUniversalIdentifier) {
const flatViewGroupsForView = Object.values(
flatViewGroupRecordByViewUniversalIdentifier[viewUniversalIdentifier],
);
const emptyValueFlatViewGroup = flatViewGroupsForView.find(
(flatViewGroup) => flatViewGroup.fieldValue === '',
);
if (
toFlatFieldMetadata.isNullable === true &&
!isDefined(emptyValueFlatViewGroup)
) {
const highestViewGroupPosition =
highestViewGroupPositionByViewUniversalIdentifier[
viewUniversalIdentifier
];
const viewGroupId = v4();
const createdAt = new Date().toISOString();
const flatView = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: flatViewMaps,
universalIdentifier: viewUniversalIdentifier,
});
sideEffectResult.flatViewGroupsToCreate.push({
id: viewGroupId,
universalIdentifier: viewGroupId,
fieldValue: '',
position: highestViewGroupPosition + 1,
applicationUniversalIdentifier:
toFlatFieldMetadata.applicationUniversalIdentifier,
viewUniversalIdentifier,
isVisible: true,
workspaceId: toFlatFieldMetadata.workspaceId,
createdAt,
updatedAt: createdAt,
deletedAt: null,
viewId: flatView.id,
applicationId: toFlatFieldMetadata.applicationId,
});
} else if (isDefined(emptyValueFlatViewGroup)) {
sideEffectResult.flatViewGroupsToDelete.push(emptyValueFlatViewGroup);
}
}
return sideEffectResult;
};