[WHEN_RELEASED_REQUIRES_CACHE_FLUSH] Object related record logic in v2 (#14937)
# Introduction
Initial motivation here was to migrate the object related records logic
from v1 to v2, please note that now in v2 views aren't records anymore
but core engine entities
## What's done
- Added specific label identifier targeting view field logic
- Handled side effects on viewField creation with lowest position on
object label identifier mutation
- Added viewField relations in field metadate entity + handled
optimistic in builder v2
- Added view relations in object metadata entity + handled optimistic in
builder v2
- Added integration tests covering the side effects and new validation
exceptions
- Sandardized cache computation
- Coverage on object metadata creation side effect on views and view
fields
## Coverage
```ts
PASS test/integration/graphql/suites/view/view-field/object-identifier-update-side-effect-on-view-field.integration-spec.ts
View Field Resolver - Successful object metadata identifier update side effect on view field
✓ should create a view field on label identifier object metadata update if it does not exist on view (7 ms)
✓ Should not allow deleting a label identifier view field (17 ms)
✓ Should not allow destroying a label identifier view field (6 ms)
✓ Should not allow updating a label identifier view field visibility to false (8 ms)
✓ Should not allow creating a view field with a position lower than the label idenfitier view field (180 ms)
✓ Should not allow updated labelIdentifier view field with a position higher than existing other view field (346 ms)
✓ Should allow updated labelIdentifier view field with a position higher than existing other view field (434 ms)
Test Suites: 1 passed, 1 total
Tests: 7 passed, 7 total
Snapshots: 5 passed, 5 total
Time: 4.571 s, estimated 5 s
```
close https://github.com/twentyhq/core-team-issues/issues/1664
This commit is contained in:
+1
@@ -19,6 +19,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
|
||||
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
|
||||
|
||||
return {
|
||||
viewFieldIds: [],
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
defaultValue: null,
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ export const getRelationTargetFlatFieldMetadataMock = ({
|
||||
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
|
||||
|
||||
return {
|
||||
viewFieldIds: [],
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
description: 'default flat field metadata description',
|
||||
|
||||
+4
@@ -113,6 +113,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"type": "MORPH_RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<ClockDate>,
|
||||
"viewFieldIds": [],
|
||||
"workspaceId": Any<String>,
|
||||
},
|
||||
{
|
||||
@@ -145,6 +146,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"type": "RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<ClockDate>,
|
||||
"viewFieldIds": [],
|
||||
"workspaceId": Any<String>,
|
||||
},
|
||||
{
|
||||
@@ -175,6 +177,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"type": "MORPH_RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<ClockDate>,
|
||||
"viewFieldIds": [],
|
||||
"workspaceId": Any<String>,
|
||||
},
|
||||
{
|
||||
@@ -207,6 +210,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"type": "RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<ClockDate>,
|
||||
"viewFieldIds": [],
|
||||
"workspaceId": Any<String>,
|
||||
},
|
||||
],
|
||||
|
||||
+18
-19
@@ -8,6 +8,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
|
||||
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { fromFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util';
|
||||
@@ -37,28 +38,26 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceFlatMapC
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
select: {
|
||||
viewFields: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
withDeleted: true,
|
||||
relations: [],
|
||||
relations: ['viewFields'],
|
||||
});
|
||||
|
||||
const flatFieldMetadataMaps = fieldMetadatas.reduce<
|
||||
FlatEntityMaps<FlatFieldMetadata>
|
||||
>((flatEntityMaps, field) => {
|
||||
const flatFieldMetadata =
|
||||
fromFieldMetadataEntityToFlatFieldMetadata(field);
|
||||
return fieldMetadatas.reduce(
|
||||
(flatFieldMetadataMaps, fieldMetadataEntity) => {
|
||||
const flatFieldMetadata =
|
||||
fromFieldMetadataEntityToFlatFieldMetadata(fieldMetadataEntity);
|
||||
|
||||
return {
|
||||
byId: {
|
||||
...flatEntityMaps.byId,
|
||||
[flatFieldMetadata.id]: flatFieldMetadata,
|
||||
},
|
||||
idByUniversalIdentifier: {
|
||||
...flatEntityMaps.idByUniversalIdentifier,
|
||||
[flatFieldMetadata.universalIdentifier]: flatFieldMetadata.id,
|
||||
},
|
||||
};
|
||||
}, EMPTY_FLAT_ENTITY_MAPS);
|
||||
|
||||
return flatFieldMetadataMaps;
|
||||
return addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatFieldMetadata,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
},
|
||||
EMPTY_FLAT_ENTITY_MAPS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ export const fieldMetadataRelationProperties = [
|
||||
'fieldPermissions',
|
||||
'indexFieldMetadatas',
|
||||
'object',
|
||||
'viewFields',
|
||||
] as const satisfies (keyof FieldMetadataEntity)[];
|
||||
|
||||
export type FieldMetadataEntityRelationProperties =
|
||||
@@ -16,5 +17,5 @@ export type FieldMetadataEntityRelationProperties =
|
||||
export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
|
||||
Omit<FieldMetadataEntity<T>, FieldMetadataEntityRelationProperties> & {
|
||||
universalIdentifier: string;
|
||||
// Could add indexField metadatas and so on ?
|
||||
viewFieldIds: string[];
|
||||
};
|
||||
|
||||
+6
-5
@@ -6,8 +6,8 @@ import {
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/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 { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
@@ -30,10 +30,11 @@ export const findAllOthersMorphRelationFlatFieldMetadatasOrThrow = ({
|
||||
);
|
||||
}
|
||||
|
||||
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
|
||||
});
|
||||
|
||||
return objectFlatFieldMetadatas.filter(
|
||||
(
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
type FindObjectFieldsInFlatFieldMetadataMapsArgs = {
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
};
|
||||
export const findObjectFlatFieldMetadatasOrThrow = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
}: FindObjectFieldsInFlatFieldMetadataMapsArgs): {
|
||||
objectFlatFieldMetadatas: FlatFieldMetadata[];
|
||||
} => {
|
||||
const objectFlatFieldMetadataMaps = getSubFlatEntityMapsOrThrow({
|
||||
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const objectFlatFieldMetadatas = Object.values(
|
||||
objectFlatFieldMetadataMaps.byId,
|
||||
).filter(isDefined);
|
||||
|
||||
return {
|
||||
objectFlatFieldMetadatas,
|
||||
};
|
||||
};
|
||||
+6
-5
@@ -6,6 +6,7 @@ import {
|
||||
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
|
||||
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
|
||||
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
|
||||
|
||||
@@ -140,10 +140,11 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
|
||||
flatEntityMaps: existingFlatObjectMetadataMaps,
|
||||
flatEntityId: flatIndex.objectMetadataId,
|
||||
});
|
||||
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
|
||||
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: existingFlatFieldMetadataMaps,
|
||||
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
|
||||
});
|
||||
|
||||
const newIndex = generateFlatIndexMetadataWithNameOrThrow({
|
||||
flatObjectMetadata,
|
||||
|
||||
+3
@@ -20,6 +20,9 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
|
||||
|
||||
return {
|
||||
...fieldMetadataWithoutRelations,
|
||||
viewFieldIds: fieldMetadataEntity.viewFields.map(
|
||||
(viewField) => viewField.id,
|
||||
),
|
||||
universalIdentifier:
|
||||
fieldMetadataWithoutRelations.standardId ??
|
||||
fieldMetadataWithoutRelations.id,
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ export const getDefaultFlatFieldMetadata = ({
|
||||
const createdAt = new Date();
|
||||
|
||||
return {
|
||||
viewFieldIds: [],
|
||||
description: createFieldInput.description ?? null,
|
||||
id: fieldMetadataId,
|
||||
icon: createFieldInput.icon ?? null,
|
||||
|
||||
+6
-5
@@ -1,8 +1,8 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
|
||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/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 { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
|
||||
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
|
||||
@@ -35,10 +35,11 @@ export const recomputeIndexOnFlatFieldMetadataNameUpdate = ({
|
||||
return [];
|
||||
}
|
||||
|
||||
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
|
||||
});
|
||||
const optimisticObjectFlatFieldMetadatas = objectFlatFieldMetadatas.map(
|
||||
(flatFieldMetadata) => {
|
||||
if (flatFieldMetadata.id === fromFlatFieldMetadata.id) {
|
||||
|
||||
+6
-5
@@ -4,13 +4,13 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
@@ -50,10 +50,11 @@ export const validateFlatFieldMetadataNameAvailability = ({
|
||||
remainingFlatEntityMapsToValidate?: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}): FlatFieldMetadataValidationError[] => {
|
||||
const errors: FlatFieldMetadataValidationError[] = [];
|
||||
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
|
||||
});
|
||||
const reservedCompositeFieldsNames = getReservedCompositeFieldNames(
|
||||
objectFlatFieldMetadatas,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user